- Home /
Getting a variable from a specific gameobject for another script
So i have two scripts - a turret script and a projectile script. My turret always targets the closest enemy there is, and i want my projectile to move towards the enemy when spawned by the turret. I want to use the Vector3.MoveTowards(); function to make it fly towards the enemy, and that would work, if i could just get the target value from my turret.
I get that i have to find the gameobject turret by refferencing to it, but when i have selveral turrets each with their own target value i bump into a problem. Could i maybe search for a gameobject with the name turret, that also has the posotion of my projectile?
If there is an easier solution id like to hear it. this is the important parts of the two scripts (c#):
Turret script:
void Update() {
if (cooldownTimer > 0)
{
cooldownTimer += -1 * Time.deltaTime;
}
if (AttackClosest)
{
float dist = CalculateDistance(FindClosestEnemy().transform.position, gameObject.transform.position);
if (dist <= range)
{
TargetClosestEnemy();
if (cooldownTimer <= 0)
{
Instantiate(projectile, transform.position, transform.rotation * Quaternion.Euler(0, 0, 90));
cooldownTimer = cooldown;
}
}
}
}
Projectile script:
void Update ()
{
Vector3.MoveTowards(transform.position, target, speed*Time.deltaTime);
}
Answer by pako · Jan 11, 2018 at 11:02 AM
Your FindClosestEnemy()
method should return a Vector3
that would be the position of the closest enemy found, which would then be used in the projectile script as a target. The target would be set upon instantiating a projectile.
I don't know how your FindClosestEnemy()
works, so I'll give some example code, based on the code you've posted.
So:
Turret script:
void Update() {
if (cooldownTimer > 0)
{
cooldownTimer += -1 * Time.deltaTime;
}
if (AttackClosest)
{
Vector3 enemyPosition = FindClosestEnemy().transform.position; //this seems odd - calling a method and using it as a component...
float dist = CalculateDistance(enemyPosition, gameObject.transform.position);
if (dist <= range)
{
TargetClosestEnemy();
if (cooldownTimer <= 0)
{
GameObject newProjectile = Instantiate(projectile, transform.position, transform.rotation * Quaternion.Euler(0, 0, 90));
newProjectile.TargetPosition = enemyPosition;
cooldownTimer = cooldown;
}
}
}
}
Projectile script:
public Vector3 TargetPosition; //set by the target upon instantiation
void Update ()
{
Vector3.MoveTowards(transform.position, TargetPosition, speed*Time.deltaTime);
}
Okay, well hopefully you can help with this too, because there is a bump on the road. I can't acces the new projectile that is being instantiated. This means that i cant get the TargetPosition from the tower script. Im getting following error
Assets/Game Components/towerAI.cs(40,35): error CS1061: Type UnityEngine.GameObject' does not contain a definition for
TargetPostion' and no extension method TargetPostion' of type
UnityEngine.GameObject' could be found. Are you missing an assembly reference?
$$anonymous$$aybe you haven't copied my code properly. In the code I've posted above, line 16: GameObject newProjectile = Instantiate( ...)
assigns the new instance created by the Instantiate( ...)
method to the newProjectile
local variable. This way you can indeed access the new projectile that is being instantiated.
Also, the error you're getting means that you haven't defined or using properly the TargetPosition
variable, per line 1 of my code in the Projectile script. Actually, you might have made a typing error, since the error says TargetPostion
, ins$$anonymous$$d of TargetPosition
, i.e. you've missed the first 'i;' in the variable name.
I fixed the misspelling and still having same problem. I also changed it to what you wrote.
public class towerAI : $$anonymous$$onoBehaviour {
public projectile projectileScript;
public GameObject projectile;
public Vector3 target;
public bool AttackClosest = false;
public int range;
public float cooldownTimer;
public float cooldown;
// Use this for initialization
void Start() {
}
// Update is called once per frame
void Update() {
if (cooldownTimer > 0)
{
cooldownTimer += -1 * Time.deltaTime;
}
if (AttackClosest)
{
float dist = CalculateDistance(FindClosestEnemy().transform.position, gameObject.transform.position);
if (dist <= range)
{
TargetClosestEnemy();
if (cooldownTimer <= 0)
{
GameObject newProjectile = Instantiate(projectile, transform.position, transform.rotation * Quaternion.Euler(0, 0, 90));
cooldownTimer = cooldown;
newProjectile.TargetPosition = target;
}
}
}
}
public class projectile : $$anonymous$$onoBehaviour {
public towerAI towerAI;
public Vector3 target;
public int speed;
public Vector3 TargetPosition; //set by the target upon instantiation
// Use this for initialization
void Start () {
target = towerAI.target;
}
// Update is called once per frame
void Update () {
Vector3.$$anonymous$$oveTowards(transform.position, TargetPosition, speed*Time.deltaTime);
}
void OnCollisionEnter2D(Collision2D coll)
{
if (coll.gameObject.tag == "Enemy")
Destroy(gameObject);
if (coll.gameObject.tag == "Wall")
Destroy(gameObject);
}
}
Answer by sisse008 · Jan 11, 2018 at 08:23 AM
if i understand correctly the turret needs to shoot the closes enemy, but you dont want the turret to miss the closes enemy? so where ever the turret shoots (other enemys for example), you want the shot to move towards the closes enemy.
if i get this right than im confused as to why you would want to design your game like this...i understand if the option to shoot is given once an enemy is close enough, but the turret should have the freedom to shoot where ever and to miss the shot.
this is what i would do:
public Rigidbody shotPrefab;
public Transform turretEnd;
void Update ()
{
if(Input.GetButtonDown("Fire1"))
{
Rigidbody shotInstance;
shotInstance = Instantiate(shotPrefab, turretEnd.position, turretEnd.rotation) as Rigidbody;
shotInstance.AddForce(turretEnd.forward * 5000);
}
}
Well... The attack closest part is just what i have for now. In the future im planning on adding some kind of array for the enemies, so that the turret can attack the furthest one in the chain. I Thought about just letting the turrets miss some shots, you know, "Its not a bug, its a feature". But i just want consistency in the game. If i have a sniper turrer that shoots from further distances it will miss a lot of the shot because of the enemies movement.
Answer by ransomink · Jan 12, 2018 at 05:30 PM
The reason his code above didn't originally work is because you copied his code exactly which had different variable names (TargetPosition) from your code (target) inside of the Projectile script. You seemed to have fixed that now but your code can be cleaned up. Your CalculateDistance() can be slimmed down a lot from
float CalculateDistance( Vector3 x, Vector3 y )
{
Vector3 dist = y - x;
float curDistance = dist.sqrMagnitude;
return curDistance;
}
to a simple line like so
float CalculateDistance( Vector3 x, Vector3 y )
{
return ( y - x ).sqrMagnitude;
}
Inside of FindClosestEnemy() method you calculate the distance again
foreach ( GameObject go in gos )
{
Vector3 diff = go.transform.position - position;
float curDistance = diff.sqrMagnitude;
if ( curDistance < distance )
{
closest = go;
distance = curDistance;
}
}
instead, use the CalculateDistance() method so you're not repeating code.
foreach ( GameObject go in gos )
{
float curDistance = CalculateDistance( go.transform.position, position );
if ( curDistance < distance )
{
closest = go;
distance = curDistance;
}
}
Also, inside the foreach loop you have an extra open/close bracket. Not needed.
After you get the closest enemy enemyPosition = FindClosestEnemy().transform.position;
you calculate the distance from the closest enemy dist = CalculateDistance( enemyPosition, gameObject.transform.position );
. This was already done within FindClosestEnemy(), so just set dist
to distance
before you return closest
.
foreach ( GameObject go in gos )
{
float curDistance = CalculateDistance( go.transform.position, position );
if ( curDistance < distance )
{
closest = go;
distance = curDistance;
}
}
dist = distance;
return closest;
Now you don't need to get the distance at all.
Where your code messes up is at TargetClosestEnemy().
void TargetClosestEnemy()
{
target = FindClosestEnemy().transform.position;
Vector3 norTar = ( target - transform.position ).normalized;
float angle = Mathf.Atan2( norTar.y, norTar.x ) * Mathf.Rad2Deg;
Quaternion rotation = new Quaternion();
rotation.eulerAngles = new Vector3( 0, 0, angle - 90 );
transform.rotation = rotation;
}
Inside the method, you find the closest enemy again, why? You found the closest enemy when you grabbed its position enemyPosition = FindClosestEnemy().transform.position;
. Doing it again can result in a different enemy from the one you already have. You don't need the target and enemyPosition Vector3 variables, only one. My version uses target instead of enemyPosition.
void TargetClosestEnemy()
{
Vector3 targetNorm = ( target - transform.position ).normalized;
float angle = Mathf.Atan2( targetNorm.y, targetNorm.x ) * Mathf.Rad2Deg;
Quaternion rotation = new Quaternion();
rotation.eulerAngles = new Vector3( 0, 0, angle - 90 );
transform.rotation = rotation;
}
Here are the two scripts: Turret.cs
public class Turret : MonoBehaviour
{
public Projectile prefab; // Prefab of a projectile. Set the type as Projectile instead of GameObject because you can access its properties without using GetComponent.
public Vector3 target; // Enemy position
public float range; // Attack range
public float cooldown; // Rate of fire
public float cooldownTimer; // Attack countdown
public bool AttackClosest = false; // Attack the closest enemy?
float dist; // Distance from enemy
Vector3 enemyPosition; // Enemy position? Unnecessary, you already have the target variable
void Update()
{
if ( cooldownTimer > 0 )
{
cooldownTimer += -1 * Time.deltaTime;
}
// Attack?
if ( AttackClosest )
{
target = FindClosestEnemy().transform.position;
// In range?
if ( dist <= range )
{
TargetClosestEnemy();
if ( cooldownTimer <= 0 )
{
// Create a projectile and set its target position
var projectile = Instantiate( prefab, transform.position, transform.rotation * Quaternion.Euler( 0, 0, 90 ) );
// Set the target variable in the Projectile script. No need for GetComponent. (Can only access public variables)
projectile.target = target;
cooldownTimer = cooldown;
}
}
}
}
/// <summary>
/// Return the distance between two positions.
/// </summary>
/// <param name="x">End position.</param>
/// <param name="y">Start position.</param>
/// <returns></returns>
float CalculateDistance( Vector3 x, Vector3 y )
{
return ( y - x ).sqrMagnitude;
}
/// <summary>
/// Return the closest enemy.
/// </summary>
/// <returns></returns>
GameObject FindClosestEnemy()
{
float distance = Mathf.Infinity;
Vector3 position = transform.position;
GameObject[] gos = GameObject.FindGameObjectsWithTag( "Enemy" );
GameObject closest = null;
foreach ( GameObject go in gos )
{
float curDistance = CalculateDistance( go.transform.position, position );
if ( curDistance < distance )
{
closest = go;
distance = curDistance;
}
}
dist = distance;
return closest;
}
/// <summary>
/// Set target location to the closest enemy.
/// </summary>
void TargetClosestEnemy()
{
Vector3 targetNorm = ( target - transform.position ).normalized;
float angle = Mathf.Atan2( targetNorm.y, targetNorm.x ) * Mathf.Rad2Deg;
Quaternion rotation = new Quaternion();
rotation.eulerAngles = new Vector3( 0, 0, angle - 90 );
transform.rotation = rotation;
}
}
Projectile.cs
public class Projectile : MonoBehaviour
{
public Vector3 target;
public float speed;
void Update()
{
Vector3.MoveTowards( transform.position, target, speed * Time.deltaTime );
}
}
Your answer
Follow this Question
Related Questions
Deserializing a JSON file yields 0 when referencing the variables in another script 2 Answers
Get an Object to move to a Position for a Fixed time 2 Answers
How Do I Access and Change Items in a List on Another Script? 2 Answers
How to share a variable through multiple copys of the same script 2 Answers
FileStream/BinaryFormatter save static variables without PlayerPrefs 1 Answer