How do I properly detect game objects? - c#

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;
}
}

Related

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

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.

Stacking effects & powerups in UI | Unity C#

How could I make it so, for example, if only one thing is on it will move to the first slot, I think you know what I mean... So how could I make this?1
I thought about using arrays but I don't understand how.
This is my code currently, rn it just shows and hides but doesnt move them so there just a gap.
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
public class UI : MonoBehaviour
{
public Text myText;
public GameObject Player;
public GameObject SugarIcon;
public GameObject DashIcon;
public GameObject JumpIcon;
public GameObject ShieldIcon;
private string dashreadystring;
private string doublejumpstring;
private string triplejumpstring;
private string shieldbuffstring;
private int iconamount = 0;
void Start()
{
GameObject Player = GameObject.Find("Player");
}
void Update()
{
PlayerScript PlayerScript = Player.GetComponent<PlayerScript>();
bool dashready = PlayerScript.canDash;
float sugarbuff = PlayerScript.sugarbuffactive;
int airJumpCount = PlayerScript.airJumpCount;
int airJumpCountMax = PlayerScript.airJumpCountMax;
bool canDoubleJump = PlayerScript.canDoubleJump;
bool shieldon = PlayerScript.shieldon;
float score = Player.transform.position.x;
// If Dash is ready, show icon, if not, hide it.
if (dashready == true)
{
DashIcon.GetComponent<Renderer>().enabled = true;
}
else
{
DashIcon.GetComponent<Renderer>().enabled = false;
}
// If Sugarbuff is active, show icon, if not, hide it.
if (sugarbuff == 1.5)
{
SugarIcon.GetComponent<Renderer>().enabled = true;
}
else
{
SugarIcon.GetComponent<Renderer>().enabled = false;
}
// If can jump,
if ((airJumpCount < airJumpCountMax) | ((canDoubleJump) && (sugarbuff == 1.5f)))
{
JumpIcon.GetComponent<Renderer>().enabled = true;
}
else
{
JumpIcon.GetComponent<Renderer>().enabled = false;
}
if (shieldon)
{
ShieldIcon.GetComponent<Renderer>().enabled = true;
}
else
{
ShieldIcon.GetComponent<Renderer>().enabled = false;
}
}
}
How could I make this? Thanks.
If your icons are children of a parent container that uses a HorizontalLayoutGroup component, it will automatically line up your icons horizontally (use VerticalLayoutGroup or GridLayoutGroup to autoalign in different patterns).
Then, if you disable the gameObject of the icons, they will disappear. The HorizontalLayoutGroup will rearrange the remaining icons in the way I think you want. When you reenable the icon, it will be reinserted into the horizontal layout.
Notes:
You are currently disabling the Renderer Component on the gameObject. This does make the object invisible, but it still exists and takes up space. Instead, disable the gameObject for the icon itself (SugarIcon.enabled = false). Hopefully the Icon objects are just images, and have no scripts/logic on them, but if so, you'll need to decouple that.
It looks like you're using GetComponent() calls many times every frame. Don't do this -- GetComponent() calls have a pretty big overhead. Instead, call it once, when you initialize your manager, and then cache the result. This will save a ton of processing.
// Call GetComponent() and cache the reference one time (when you initialize the objects).
Renderer sugarIconRenderer = SugarIcon.GetComponent<Renderer>();
// Call the cached reference every frame when you need it.
sugarIconRenderer.enabled = false;

The following code shouldn't work, yet it does?

This code makes no sense to me, it's probably stupid mistake on my side. But if someone could explain the error I made, that would be great.
using System.Collections;
using UnityEngine;
public class Tower : MonoBehaviour
{
[SerializeField] Transform objectToPan = default;
[SerializeField] Transform enemyTarget = default;
[SerializeField] ParticleSystem BulletParticles = default;
[SerializeField] EnemyDamage StopIfDead = default; //Another Script
bool isNotDead = true;
const int maxShootRange = 30;
void Update()
{
TowerShoots();
}
void TowerShoots()
{
float distanceBetweenBoth = Vector3.Distance(objectToPan.transform.position, enemyTarget.transform.position);
if (distanceBetweenBoth >= maxShootRange)
{
BulletParticles.Play();
}
if (StopIfDead.isDead == true)
{
WhenDead();
}
}
void WhenDead()
{
const int DeathRota = 0;
isNotDead = false;
BulletParticles.Stop();
objectToPan.transform.rotation = Quaternion.Euler(DeathRota, DeathRota, DeathRota);
}
}
First major thing I noticed is that
if(distanceBetweenBoth >= maxShootRange)
{
//Stuff
}
Doesn't this mean that, if the distance is greater, or equal to, than play the particles. The problem is that, if the distance is greater than the maxShootRange (30), than it won't shoot above 30. The problem is that if the distance between these two object is greater than, or equal to, the maxShootRange (30), than it won't shoot something that is further than maxShootRange. It actually shoots under the 30 and below. I have tried many different type of ways, non of which seem to work. I tried to switch the order of objectToPan with enemyTarget. Yet it basically does the same thing. If I make an else statement saying not to shoot. Than the code completely stops shooting. I tried to turn on and off PlayOnAwake and see what effect that had. But now I am not sure.
else
{
//Stop the Bullets
}
If you need more information about the Particle System settings and stuff, ask me.

Text is supposed to be displayed when childCount equals 0, but it doesn't

this should be a very simple answer. I'm following a Unity with C# tutorial for making a simple Space Invaders game, and at one point it is shown that when our enemyHolder object has no child objects left (when all enemies are destroyed) the attached text under the winText function should be displayed.
So we have
if (enemyHolder.childCount == 0)
{
winText.enabled = true;
}
When I run the code the text isn't displayed after the enemies are destroyed and no child object is left. It's like the code stops getting read at that point, although the character is still movable and you can generate new shots.
If I create two "Enemy" child objects and tell it to display the winText rather when the childCount reaches 1, it does work.
So why is it not working when the function calls for == 0?
EDIT: Complete class code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class EnemyController : MonoBehaviour
{
private Transform enemyHolder;
public float speed;
public GameObject shot;
public Text winText;
public float fireRate = 0.997f;
// Start is called before the first frame update
void Start()
{
winText.enabled = false;
InvokeRepeating("MoveEnemy", 0.1f, 0.3f);
enemyHolder = GetComponent<Transform>();
}
void MoveEnemy()
{
enemyHolder.position += Vector3.right * speed;
foreach (Transform enemy in enemyHolder)
{
if (enemy.position.x < -10.5 || enemy.position.x > 10.5)
{
speed = -speed;
enemyHolder.position += Vector3.down * 0.5f;
return;
}
if (enemy.position.y <= -4)
{
GameOver.isPlayerDead = true;
Time.timeScale = 0;
}
if (enemyHolder.childCount == 1)
{
CancelInvoke();
InvokeRepeating("MoveEnemy", 0.1f, 0.25f);
}
if (enemyHolder.childCount == 0)
{
winText.enabled = true;
}
}
}
}
Your code is inside the void MoveEnemy() function.
I'm assuming your script is attached to in-game enemies. Your code doesn't run because the MoveEnemy function no longer runs if there are no enemies.
So, you need to handle enemy movement and scene handling in different scripts.
The code that checks the enemy holder's number of children should be placed inside a void Update() function. This Update() function should be placed on an object that never gets deleted. Its advantage is that it runs every frame.
As a convention, devs generally use separate empty objects or even the camera to attach scripts which contain Update functions that handle the scene. Good luck!
Read more on Update

Error accessing property across classes: an object reference is required for the non-static field method or property

I have been dabbling in unity and have run into a problem as I an unable to figure out how to notify another class that a ability has been used and the countdown is active. I realize that it is because I need to be making a static reference but not quite sure how to use one while still being able to change the value. I will include just the important bits so that you don't have to waste your time. (I want to be able to have the usedAbilities.canFireballnow be equal to that as when I call it in the fireball script.)
fireball
float canuseFireballtimer = 0;
bool startCooldown = false;
// Update is called once per frame
void Update()
{
if (startCooldown) {
usedAbilities.canFireballnow = false; // error
canuseFireballtimer += Time.deltaTime;
if (canuseFireballtimer >= 5) {
usedAbilities.canFireballnow = true; //error
}
}
if (Input.GetKeyDown(KeyCode.Q) && enoughmana && usedAbilities.canFireballnow) { // error
startCooldown = true;
ManaBar.mana -= 10f;
Instantiate(fireballPrefab, fireballSpawn.position, fireballSpawn.rotation);
}
}
usedAbilities script
public bool canFireballnow = true;
Thanks,
A fellow Programmer
First of all, you need to add a reference to your object. You can do that by using [SerializeField] before creating a variable of type GameObject. Like this:
[SerializeField]
GameObject obj;
Then, in Unity, you can drag the GameObject from the Hierarchy to the Inspector.
This is what you should see in the Inspector:
Then, you need to get the script component of the GameObject to finally be able to read it's value.
obj.GetComponent<name_of_your_script>().value;
Your final code should look like this:
[SerializeField]
GameObject abilities;
void Update() {
if (canuseFireballtimer >= 5) {
abilities.GetComponent<usedAbilities>().canFireballnow = true;
}
Note: Your variable should be public.

Categories