Collision Detection in Child Colliders, From Parent Object (Unity3D) - c#

I'm trying to make a script that allows the head hitbox, and the chest hitbox which are children to the main game object, to be detected by the main script so that damage can take place. For example, the main script knows when the head collider is hit, vs the body collider. (Currently, doesn't work even if enabled). I've tried many scripts and searched for answers and I really can't find them. Please help. Note, the comments as far as I know have little to nothing to do with the issue I'm having. (Edit): Clarified which part of the script I'm having trouble with.
Here's the part of the script that is giving me trouble:
public void OnTriggerEnter(Collider collider)
{
myLastHit = collider.gameObject.GetComponent<PunchDetection>().punched;
foreach(Transform sibling in transform.parent)
{
if(sibling.name == "HeadHitbox")
{
collider.enabled = true;
//Collider col = child.GetComponent<SphereCollider>();
if (canTakeDamage && myLastHit is SphereCollider)
{
TakeDamage(15);
StartCoroutine(damageTimer());
}
}
if (sibling.name == "RibsHitbox")
{
collider.enabled = true;
//Collider col = child.GetComponent<CapsuleCollider>();
if (canTakeDamage && myLastHit is CapsuleCollider)
{
TakeDamage(5);
StartCoroutine(damageTimer());
}
}
}
}
public void TakeDamage(int damage)
{
currentHealth -= damage;
healthBar.SetHealth(currentHealth);
}
private IEnumerator damageTimer()
{
canTakeDamage = false;
yield return new WaitForSeconds(damageTimeout);
canTakeDamage = true;
}```

Why not [SerialisedField] protected RigidBody headHitChhildObject, ribHitChhildObject;
Drag and drop to inspector. Than in start function.
headHitChhildObject = findObjectOftType ().gameObject.GetComponent()
ribHitChhildObject= findObjectOfType (). gameObject.GetComponent ()
(Do this seperate for each object)
Change take damage parameters to
Public Void TakeDamage (Collider triggerObject) --- I would use a float personally for damage/health, but that's just me.
In on trigger function.
TakeDamage(collision)
Inside Take Damage Fumction have the following lines
For if (triggerObject.attachedRigidBody ! = null && siblings.name = = putname)
currentHealth - = damage amount
For the Else If(triggerObject. attachedRigidBody ! = null && siblings.name = = putOtherName)
currentHealth - = damage amount
(My more optimal suggestion) Or better yet, have a Master script for
handling the hits.(listener) Pretty much take damage and numerator.
public Void On Enable() MyGameEvents.onTriggerColliderEvent +=
TakeDamage ;
public Void On Disable()
MyGameEvents.onTriggerColliderEvent -= TakeDamage ;
a delegation script for handling events across scripts(not just these
scripts, but other scripts, instead of constant cross referencing
multiple times and long code!
public static class MyGameEvents or something you chose
public delegate void OnTriggerCollider(Collider triggerObject,
int amount) ;
Public static OnTriggerCollider onTriggerColliderEvent ;
In a script on each trigger children components just have;
[SerialisedField] protected int damageToCall // Set in the inspector,
so you can experiment with this)
OnTriggerEnter(Collider collider)
MyGameEvents.onTriggerColliderEvwnr?.Invoke(Collider, damageToCall)
and you can use the public static MyGameEvents for handling and coding that is reoccurring across other scripts. Such as camera shake, ground shake, body part shake, health regen when certain req's are met. Power Up Events, object destroy on Zero health events.
Score points, etc.
Think of it as a way more reusable and versatile unity events all the YouTube dev wannabes don't use properly.

Related

I am having issues with coding a game of tag

So I am trying to make my first game using Unity and c#. I want my game to be a simple game of tag like I used to play when I was younger. I have tried using "OnCollisionEnter" and I was able to get that to change a counter that gave a bool a label. I realized that while this may work for tagging someone it does not help with other people tagging you. And tips on how I can make my code more like a "Tag Manager"?My current progress
you can have a Taggable.cs like this, not sure if this is even correct syntax, I don't care to actually write it out and test
class Taggable : MonoBehaviour {
bool it;
private void OnCollisionEnter(Collision collision) {
var other = collision.collider.GetComponent<Taggable>();
if(other != null) {
if(it) {
other.it = true;
it = false;
}else if(other.it) {
it = true;
other.it = false;
}
}
}
}
You could implement the logic such that your script checks to see if you are the tagger using Unity's tags property. Then, you could switch the tags of both the players, once you check if the collided object is a player.
Below is the code I suggest you use. I would personally use OnTriggerEnter to have more clearer logic between collider and player. Make sure to have a collider object and a Rigidbody on all players within the scene for the function to be called, and attach your script onto every player within the scene.
private void OnTriggerEnter (Collider col) {
// if you are the tagger
if (gameObject.tag == "Tagger") {
// and if the collided object is a regular player
if (col.tag == "Player") {
// the player is now a tagger
col.tag = "Tagger";
// depending on if you want to buildup taggers or switch them around,
// the below assignment would vary
gameObject.tag = "Player";
}
}
}

Run code on every frame two colliders are in contact

im trying to make it so that if my character continues to touch a gameobject with a damage script the player continuesly gets damaged. instead of this result i only get damaged once when touching the gameobject. there are no error messages. ive tried to replace the if with a while loop and it ended up crashing my game. is there any way to loop a if statement preferably with a way to time it.
if (other.tag == "Player")
{
healthScript.healthPoints -= damage;
}
this is the if statement im trying to loop.
First, I am under the assumption you are using Colliders as triggers with the Is Trigger attribute selected. I am also assuming this is a 2D game. If not, this same approach will work, you will just have to change the methods from 2D to 3D.
You will want to add the OnTriggerEnter2D() and OnTriggerExit2D() methods to your player health script. These will allow us to detect when the player is standing on the damaging object. From here, we will start a coroutine which can be used to deal damage in a timed manner.
using System.Collections;
using UnityEngine;
public class HealthScript : MonoBehaviour
{
public float healthPoints = 100f;
public float damage = 5f;
public bool OnDamagingObject = false;
IEnumerator DealDamage()
{
while (OnDamagingObject)
{
healthPoints -= damage;
yield return new WaitForSeconds(1f);
}
}
void OnTriggerEnter2D(Collider2D hitInfo)
{
GameObject collider = hitInfo.gameObject;
if (collider.tag == "DamagingObject")
{
OnDamagingObject = true;
StartCoroutine(DealDamage());
}
}
void OnTriggerExit2D(Collider2D hitInfo)
{
GameObject collider = hitInfo.gameObject;
if (collider.tag == "DamagingObject")
{
OnDamagingObject = false;
}
}
}
For those who wish to use OnTriggerStay(), I will also provide a solution for that. This time, we will keep the script on the damaging object, following the structure the question asker is using.
using UnityEngine;
public class DamageScript : MonoBehaviour
{
public HealthScript healthScript;
public float damage = 5f;
void OnTriggerStay2D(Collider2D hitInfo)
{
GameObject other = hitInfo.gameObject;
if (other.CompareTag("Player"))
{
healthScript.healthPoints -= damage;
}
}
}
As mentioned in the comments of my other answer, implementing both OnTriggerEnter() and OnTriggerExit() allow more control over dealing damage to the player. One of the downsides to using OnTriggerStay() is I am unsure how to deal the damage in a timed manner. Additionally, OnTriggerStay() limits how you can deal damage to the player. If in the future you wanted to give 5 damage to the player when they first touch the damaging object but 2 damage for every second thereafter while touching it, it is not possible to do so using OnTriggerStay().

How do I properly detect game objects?

My goal is to write one script that I can use on different game objects and it should have specific variables tied to it on that game object only without affecting other scripts in the process.
For example, if I take this script and put it on two game objects each game object should have their own unique variable value in that same script.
If my question is not clear enough, I'm more than happy to elaborate further.
I have a good understanding of the Unity Editor, however, I'm pretty new to C# so I don't think it's unreasonable that I made a rookie mistake somewhere in my code.
The way I've got things setup is that I have two separate scripts:
Fighting controls the values like the Team, Health, Attack Damage, Cool Down, Cooling down and Snap
TrigDetect controls the detection of a trigger being activated as a result of an enemy entering the trigger radius.
The problem I'm currently having lies in the TrigDetect script I guess.
It should also be noted that an empty attached to each game object in question contains both of these scripts and is tagged as "Troop".
TrigDetect
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TrigDetect : MonoBehaviour
{
//public GameObject[] Enemy;
bool once = false;
void OnTriggerEnter(Collider other)
{
if (other.CompareTag("Troop"))
{
//Debug.Log("Entered");
}
}
void OnTriggerExit(Collider other)
{
if (other.CompareTag("Troop"))
{
//Debug.Log("Exitted");
}
}
void OnTriggerStay(Collider other)
{
if (other.CompareTag("Troop"))
{
Fighting self = GetComponent<Fighting>();
GameObject g = GameObject.Find("Detection");
Fighting fScript = g.GetComponent<Fighting>();
//Enemy = GameObject.FindGameObjectsWithTag("Troop");
//Debug.Log("Staying");
//Debug.Log(Enemy);
//Debug.Log(self.Health);
//Debug.Log(fScript.Health);
if (once == false)
{
Debug.Log("I am the team:" + self.Team);
Debug.Log("I have detected the team:" + fScript.Team);
once = true;
}
if (self.Team != fScript.Team)
{
if (self.CoolingDown == false)
{
self.CoolingDown = true;
fScript.Health -= self.AttackDamage;
}
else
{
self.CoolDown -= Time.deltaTime;
if (self.CoolDown <= 0)
{
self.CoolingDown = false;
self.CoolDown = self.original;
}
}
}
}
}
}
Fighting
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Fighting : MonoBehaviour
{
public int Team = 1;
public int Health = 100;
public int AttackDamage = 10;
public float CoolDown = 2;
public float original = 2;
public bool CoolingDown = false;
public bool Snap = false;
// Update is called once per frame
void Update () {
if (Snap == true || Health <= 0)
{
//Destroy(gameObject, .5f);
Destroy(transform.parent.gameObject);
}
if (Input.GetKey(KeyCode.N)) Instantiate(transform.parent.gameObject);
}
}
The expected result when I move one game object into the trigger radius of the other is that they should both start subtracting Health from each other based on the AttackDamage value. They should do this every time the CoolingDown value is false. When an attack is executed, it's flipped to true and a timer starts, when the timer is done it's flipped back to false.
However, upon moving the two objects into each other's radius', the first object has its health taken away as expected and then proceeds to do nothing until it's health reaches 0 then it dies because of the object attacking it. The object attacking is successfully attacking the other object but, is still not being affected by the object it's attacking.
Basically, Find(name) only returns the first instance of anything by that name, thus your g = Find(name) is almost guaranteed to never be the object related to your trigger/collision condition. The OnTriggerStay(Collider other) already gives you the 'other' collider that's in your trigger zone, so use it. :)
Replace this:
GameObject g = GameObject.Find("Detection");
Fighting fScript = g.GetComponent<Fighting>();
with this:
Fighting fScript = other.GetComponent<Fighting>();
To your question header:
Every instaced (non-static) value is allways unique to the according component and thereby to the according GameObject it is attached to. You might want to refrase the question because this is actually not your issue.
The problem is that when you do
GameObject.Find("Detection");
it actually finds the same object both times: Namely the first one in the hierarchy. So in one of of the two components you find your own empty object and skip the rest in
if(self.Team != FScript.Team)
.. you could try to use
other.Find("Detection");
instead to only search in the according context .. However, you should not use Find at all!
It is very performance intense
You should allways reuse references and not search them over and over again
You don't need it in your case
Since you say both scripts are attached to the same object you can simply use
GetComponent<Fighting>();
and you can do so already in Awake and reuse the reference instead:
private Fighting myFighting;
private void Awake()
{
myFighting = GetComponent<Fighting>();
}
Than for the collision you don't have to use Find either because you already have the reference of the object you collide with: other.gameObject. I don't know your entire setup but you can search for the component either downwards in the hierachy
// the flag true is sued to also find inactive gameObjects and components
// leave it without parameters if you don't want this
var otherFighting = other.GetComponentInChildren<Fighting>(true);
or searcg upwards in the hierachy
var otherFighting = other.GetComponentInParent<Fighting>(true);
or if you already know you collide exactly with the correct GameObject anyway simply use
var otherFighting = other.GetComponent<Fighting>();
I will use the latter in my example.
Than cheking the health all the time in Update is a huge perfomance issue. You should rather have a method e.g. TakeDamage and do your check only if your health is actually changed:
Fighting
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Fighting : MonoBehaviour
{
public int Team = 1;
public int Health = 100;
public int AttackDamage = 10;
public float CoolDown = 2;
public float original = 2;
// you don't need that flag see below
//public bool CoolingDown = false;
public bool Snap = false;
private void Update()
{
// you might also put this in a callback instead of update at some point later
if(Snap == true)
{
Destroy(transform.parent.gameObject);
}
// Note: this also makes not muh sense because if you destroyed
// the parent than you cannot instantiate it again!
// use a prefab instead
if (Input.GetKey(KeyCode.N)) Instantiate(transform.parent.gameObject);
}
public void TakeDamge(int DamageAmount)
{
Health -= DamageAmount;
if (Health > 0) return;
Destroy(transform.parent.gameObject);
}
}
Another performance issue in general: Even if Start, Update etc are empty, if they are present in your script Unity will call them. So if you don't use them then completely remove them to avoid that useless overhead.
So I would have
TrigDetect
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TrigDetect : MonoBehaviour
{
bool once = false;
private Fighting myFighting;
private void Awake()
{
myFighting = GetComponent<Fighting>();
}
void OnTriggerStay(Collider other)
{
// if wrong tag do nothing
if (!other.CompareTag("Troop")) return;
Fighting fScript = other.GetComponent<Fighting>();
// here you should add a check
if(!fScript)
{
// By using the other.gameObject as context you can find with which object
// you collided exactly by clicking on the Log
Debug.LogError("Something went wrong: Could not get the Fighting component of other", other.gameObject);
}
if (!once)
{
Debug.Log("I am the team:" + self.Team);
Debug.Log("I have detected the team:" + fScript.Team);
once = true;
}
// if same team do nothing
if (self.Team == fScript.Team) return;
// you don't need the CoolingDown bool at all:
self.CoolDown -= Time.deltaTime;
// if still cooling down do nothing
if(self.CoolDown > 0) return;
fScript.TakeDamage(self.AttackDamage);
self.CoolDown = self.original;
}
}

interconnecting scripts in unity3D using the preprocessor [duplicate]

I've searched around and I just can't get this to work. I think I just don't know the proper syntax, or just doesn't quite grasp the context.
I have a BombDrop script that holds a public int. I got this to work with public static, but Someone said that that is a really bad programming habit and that I should learn encapsulation. Here is what I wrote:
BombDrop script:
<!-- language: c# -->
public class BombDrop : MonoBehaviour {
public GameObject BombPrefab;
//Bombs that the player can drop
public int maxBombs = 1;
// Update is called once per frame
void Update () {
if (Input.GetKeyDown(KeyCode.Space)){
if(maxBombs > 0){
DropBomb();
//telling in console current bombs
Debug.Log("maxBombs = " + maxBombs);
}
}
}
void DropBomb(){
// remove one bomb from the current maxBombs
maxBombs -= 1;
// spawn bomb prefab
Vector2 pos = transform.position;
pos.x = Mathf.Round(pos.x);
pos.y = Mathf.Round(pos.y);
Instantiate(BombPrefab, pos, Quaternion.identity);
}
}
So I want the Bomb script that's attached to the prefabgameobject Bombprefab to access the maxBombs integer in BombDrop, so that when the bomb is destroyed it adds + one to maxBombs in BombDrop.
And this is the Bomb script that needs the reference.
public class Bomb : MonoBehaviour {
// Time after which the bomb explodes
float time = 3.0f;
// Explosion Prefab
public GameObject explosion;
BoxCollider2D collider;
private BombDrop BombDropScript;
void Awake (){
BombDropScript = GetComponent<BombDrop> ();
}
void Start () {
collider = gameObject.GetComponent<BoxCollider2D> ();
// Call the Explode function after a few seconds
Invoke("Explode", time);
}
void OnTriggerExit2D(Collider2D other){
collider.isTrigger = false;
}
void Explode() {
// Remove Bomb from game
Destroy(gameObject);
// When bomb is destroyed add 1 to the max
// number of bombs you can drop simultaneously .
BombDropScript.maxBombs += 1;
// Spawn Explosion
Instantiate(explosion,
transform.position,
Quaternion.identity);
In the documentation it says that it should be something like
BombDropScript = otherGameObject.GetComponent<BombDrop>();
But that doesn't work. Maybe I just don't understand the syntax here. Is it suppose to say otherGameObject? Cause that doesn't do anything. I still get the error : "Object reference not set do an instance of an object" on my BombDropScript.maxBombs down in the explode()
You need to find the GameObject that contains the script Component that you plan to get a reference to. Make sure the GameObject is already in the scene, or Find will return null.
GameObject g = GameObject.Find("GameObject Name");
Then you can grab the script:
BombDrop bScript = g.GetComponent<BombDrop>();
Then you can access the variables and functions of the Script.
bScript.foo();
I just realized that I answered a very similar question the other day, check here:
Don't know how to get enemy's health
I'll expand a bit on your question since I already answered it.
What your code is doing is saying "Look within my GameObject for a BombDropScript, most of the time the script won't be attached to the same GameObject.
Also use a setter and getter for maxBombs.
public class BombDrop : MonoBehaviour
{
public void setMaxBombs(int amount)
{
maxBombs += amount;
}
public int getMaxBombs()
{
return maxBombs;
}
}
use it in start instead of awake and dont use Destroy(gameObject); you are destroying your game Object then you want something from it
void Start () {
BombDropScript =gameObject.GetComponent<BombDrop> ();
collider = gameObject.GetComponent<BoxCollider2D> ();
// Call the Explode function after a few seconds
Invoke("Explode", time);
}
void Explode() {
//..
//..
//at last
Destroy(gameObject);
}
if you want to access a script in another gameObject you should assign the game object via inspector and access it like that
public gameObject another;
void Start () {
BombDropScript =another.GetComponent<BombDrop> ();
}
Can Use this :
entBombDropScript.maxBombs += 1;
Before :
Destroy(gameObject);
I just want to say that you can increase the maxBombs value before Destroying the game object. it is necessary because, if you destroy game object first and then increases the value so at that time the reference of your script BombDropScript will be gone and you can not modify the value's in it.

How to make a projectile turn with a arc

I have a cannon that fires a bullet in a parabolic arc. Right now when I fire the bullet stays in the same rotation as it was when it fired out of the cannon.
How do I make it so the bullet's rotation follows the arc as it travels through the air?
I tried the following as a script running on the bullet
Exhibit 1
public class PointingBehaviour:MonoBehaviour
{
private Rigidbody rb;
private void Awake()
{
rb = GetComponent<Rigidbody>();
}
public void Update()
{
transform.up = rb.velocity;
}
}
And that works fairly well. but I see a slight flicker on the first frame the object exists (I think this is because the velocity is still 0 at this point) and the object spins uncontrollably once it hits the ground.
I got it to stop flickering at the start and stop spinning when it lands by doing the following
public class BulletController : MonoBehaviour
{
private Rigidbody _rb;
private bool _followArc;
private bool _firstFrame;
private void Start()
{
_rb = GetComponent<Rigidbody>();
_firstFrame = true;
_followArc = true;
}
public void LateUpdate()
{
if (_followArc && !_firstFrame)
transform.up = _rb.velocity;
_firstFrame = false;
}
public void OnCollisionEnter(Collision collision)
{
_followArc = false;
}
}
But if I happen to bump something in the air it stops following the arc and just does a free tumble till it lands. What is the "Correct" way to do what I want to do?
Because people wanted to see it, here is the code for spawning the bullet.
public class TankController : MonoBehaviour
{
private Transform _barrelPivot;
private Transform _bulletSpawn;
public GameObject Bullet;
public float FirePower;
public float RotationSpeed;
public float TiltSpeed;
private void Start()
{
_barrelPivot = GetComponentsInChildren<Transform>().First(x => x.CompareTag("BarrelPivotPoint"));
_bulletSpawn = GetComponentsInChildren<Transform>().First(x => x.CompareTag("BulletSpawn"));
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
FireCannon();
}
//(SNIP) Handle turning left and right and pivoting up and down.
}
private void FireCannon()
{
var newBullet = SpawnBullet();
var rb = newBullet.GetComponent<Rigidbody>();
rb.AddForce(_bulletSpawn.up * FirePower, ForceMode.Impulse);
}
private GameObject SpawnBullet()
{
var newBullet = (GameObject) Instantiate(Bullet, _bulletSpawn.position, _bulletSpawn.rotation);
newBullet.transform.localScale = Bullet.transform.localScale;
return newBullet;
}
}
I believe what you're saying is - your're script exhibit1 works great.
If you think about it, all you have to do is turn off that behavior, when you want to.
In this case, you're thinking "that behavior should stop when it hits the ground .. I assume that's what you mean physically.
It's very easy to turn off a MonoBehaviour, as you know just enabled = false;
So, you have some script somewhere like Bullet or perhaps BulletBoss
In there you'll have a variable private PointingBehaviour pb and you'll just pb.enabled = false
(I can't tell you "when" you want to do that, it depends on your game .. so it might be something like "when altitude is less than blah" or "when I hit something" ... probably OnCollisionEnter related.)
Note that - I'm sure you know this - for pointing behavior for a projectile, just setting it along the tangent is pretty basic.
A lovely very easy thing to do when you're writing a pointing behavior for a projectile like that, try lerping it gently to the tangent. The result is amazing real looking. Next perhaps add some random "wiggles" which is very bomb-like. It's amazing how the user can see such things, only only a few frames. (The next step up would be real air physics I guess!)
Note that, certainly, PointingBehaviour should just be its own script. You must keep behaviors totally isolated.
Regarding LateUpdate mentioned, there is never a need to use it.
Unity offer a "script order execution" system (see Preferences menu option), if one truly wants to order within the frame. But about the only reason to do that would be perhaps for some experimentation reason.
It's rather like "using a global" - there's just no reason for it. As with any code, if you have to do something in a given order, just call in order.

Categories