Turret Targeting Help

Having issues with a code for turret tracking/targeting. The idea is this:

Object A (a tank turret) should target the closest enemy vehicles first, smoothly rotate to look at that target, and track that target until it either moves out of the firing line (i.e. behind an object) or is destroyed. So… the script I have right now is as follows:

#pragma strict

var target : Transform;
var targets : GameObject[];
var targetRange : float; 
var closeTarget : GameObject; 
var distance = Mathf.Infinity;

function Start () {
	var position = transform.position;
	targets = GameObject.FindGameObjectsWithTag("Enemy"); 
	for (var target : GameObject in targets) {
		var targetRange = (target.transform.position - position);
		var rangeDistance = targetRange.sqrMagnitude;
		if (rangeDistance < distance) {
			distance = rangeDistance;
		}
	}
	return closeTarget.transform;
	Invoke ("AimAndFire", 0.0);
}

function AimAndFire () {
	transform.LookAt(target);
}

This generates no compiler errors or warning messages…and yet when in play mode… absolutely nothing happens. Any help is appreciated.

Is there something other than FindObjects that would work better? I played around with OverlapSphere but couldn’t really understand it either.

Why have a return in your start function?

I don’t think AimAndFire is ever being called, as return will not execute any remaining lines of code in the function.

Furthermore, it looks like you are trying to search for the closest enemy and make that your target? Your code seems incomplete - I’m not sure what you are assigning in the Inspector, but I’m predicting a null reference exception at some point.

Also it can be confusing to locally scope a GameObject target in your Start function when you already have Transform target declared outside of it.

  1. You never assign to target
  2. There is no return from Start (you put the code there, but that doesnt mean its returning)
  3. You never call AimAndFire
  4. Seems like you need to do some basic tutorials

Also Start() is only called once when the turret is first created. So even if the method of picking a target worked, you’d pick one target, then when it was dead never pick another one.

Every time you do something like var targetRange you’re defining a new variable within the scope of whatever function it’s in, instead of just using the targetRange property that’s attached to the turret. If you leave out the var, you’ll use the targetRange property like you probably meant to.

You use target in the loop and then later reference target in the AimAndFire function. If you were looping through the target property instead of the locally defined var target I think target would just be whichever was last in the targets array. Since you aren’t using the target property, it will have to get a value somewhere else, and like Zaladur said, if it hasn’t got a value from somewhere else you’d get a null reference error if you called the AimAndFire() function.

You might not have gotten a null reference error just because you never do call the AimAndFire() function because the Invoke follows a return.

Your AimAndFire function I would probably call from Update or FixedUpdate(). Or maybe if I thought it was better I’d set up InvokeRepeating so I could run it less frequently than the frame rate.

I would make a FindNextTarget function that I would call once the old target is destroyed or it left the turret’s range.

And I would make the FindNextTarget actually find the closest target for real instead of just the one at the end of the list. Something like this:

for (var targetIndex : GameObject in targets) { //targetIndex so it doesn't conflict with target
        var targetRange = (targetIndex.transform.position - position);
        var rangeDistance = targetRange.sqrMagnitude;
        if (rangeDistance < distance) {
            distance = rangeDistance;
            target = targetIndex; //assigns it to target if this one is closer than those previous
        }
    }

//at this point, target should now have the closest GameObject in the targets array

Is there a good tutorial for this kind of thing anywhere? I’ve been looking for one on either this or OverlapSphere and I can’t find much.

Ok well thanks. I’ll keep digging.

Hey wilhelm,

using UnityEngine;
using System.Collections;

public class TestingZone : MonoBehaviour
{
    public Transform target;

    void Update()
    {
        transform.LookAt(target);
    }
}

Here is a good starting point. Apply this script to a object, set another as the target, push play and watch it work. This takes care of 1/3 of your constraints, as it does not smoothly rotate to the target, and you are manually setting the target, as opposed to finding the nearest.

If you fully understand all this you’re ready to move on to the other 2/3 constraints.

EDIT: Just a heads up, this is C# code, and not UnityScript. You’ll have to make a C# script named TestingZone.cs or convert it to UnityScript.

Yeah the actual rotation of the turret isn’t the problem. It’s target selection and smoothness.

Okay. Here’s a pretty simple implementation of what you’re looking for.

using UnityEngine;
using System.Collections;

public class TestingZone : MonoBehaviour
{
    public float Range = 20.0f;
    public float RotateSpeed = 180.0f;

    void Update()
    {
        Transform target = ClosestInRange(Range);

        Debug.Log(target);

        if (target != null)
        {
            Vector3 direction = (target.position - transform.position).normalized;

            transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.LookRotation(direction), RotateSpeed * Time.deltaTime);
        }
    }

    Transform ClosestInRange(float r)
    {
        Collider[] targets = Physics.OverlapSphere(transform.position, r * 0.5f);
        
        Transform target = null;

        float smallestDistance = Mathf.Infinity;

        foreach (Collider col in targets)
        {
            float d = Vector3.Distance(transform.position, col.transform.position);
            if (col != collider  d < smallestDistance)
            {
                smallestDistance = d;
                target = col.transform;
            }
        }

        return target;
    }
}

The function ClosestInRange finds every object with a collider within a sphere with the radius defined. It then iterates through each one, finding out which is closest, and returns it back into the Update loop. The Update loop verifies that there is an available target. The direction between the target and the player is found by subtracting their positions (this gives you a Vector3 that points from the player to the target). The vector is normalized so that it’s magnitude (length) is equal to 1, and then we rotate towards it.

Erik

Thanks. It’ll take me some time to implement (as I anticipate difficulties in translation to Unityscript) but I’ll let you know if it works or not.

Almost got this worked out… only issue I’m having now is, ironically, the actual rotation of the turret. It tracks and targets fine… just with the wrong face. It seems to be wanting to target with the Y axis, not the naturally-forward Z axis.

Here’s the full code I’m using. I believe line 39 is the relevant one to this problem.

#pragma strict

var tankShell : GameObject;
var reloadTime : float = 1f;
var turnSpeed : float = 5f;
var firePauseTime : float = 0.001;
var myTarget : Transform;
var muzzlePositions : Transform[];
var turret : Transform;
var errorAmount : float;
private var	nextFireTime : float;
private var	nextMoveTime : float;
private var aimError : float;
private var	desiredRotation : Quaternion;

function Start () {
}

function Update () {
	if (myTarget) {
		if (Time.time >= nextMoveTime) {
			CalculateAimPosition (myTarget.position);
			turret.rotation = Quaternion.Lerp(turret.rotation, desiredRotation, Time.deltaTime*turnSpeed);
		}
		if (Time.time >= nextFireTime) {
			FireProjectile();
		}
	}
}
			
function OnTriggerEnter (other : Collider) {
	if (other.gameObject.tag == "EnemyArmor") {
		nextFireTime = Time.time+(reloadTime*0.5);
		myTarget = other.gameObject.transform;
	}
}

function CalculateAimPosition (targetPos : Vector3) {
        var aimPoint = Vector3 (targetPos.x+aimError, targetPos.y+aimError, targetPos.z+aimError);
	desiredRotation = Quaternion.LookRotation(aimPoint);
}

function CalculateAimError () {
	aimError = Random.Range (-errorAmount, errorAmount);
}

function FireProjectile () {
	nextFireTime = Time.time + reloadTime;
	nextMoveTime = Time.time + firePauseTime;
	CalculateAimError ();
	for (theMuzzlePos in muzzlePositions) {
		Instantiate (tankShell, theMuzzlePos.position, theMuzzlePos.rotation);
	}
}

LookRotation can take an “up” vector as a parameter. If the rotation is wrong you might be able to fix it with that.

Another possibility is to change the turret model itself to ensure it’s got the right side facing the way you want.

A third possibility is to create a new Quaternion and then multiply desiredRotation by that to rotate it back into place.

I’m kind of surprised you’re just basing the rotation off of myTarget.position and not, say myTarget.position-turret.position

So would that be, on line 40, changing…

desiredRotation = Quaternion.LookRotation(aimPoint);

to

desiredRotation = Quaternion.LookRotation.up(aimPoint);

or am I misunderstanding you?

Not sure what that would affect…?

If you click on the red LookRotation in the code in this forum, it should open up the Unity Script Reference for the function, and so it should show there is a second optional parameter. Right now you’re just passing aimPoint as a the first parameter for forward Vector3, but I can spin in a circle while keeping my forward pointing in the right direction, which is why they have the option to specify an upwards Vector3. This might not be your problem, it might need one of the second two approaches instead.

myTarget.position and myTarget.position-turret.position would only be the same if turret.position is (0,0,0)

If myTarget is at positon (10,0,0) which direction the turret needs to aim is going to be completely opposite if the turret is at (5,0,0) or (15,0,0) you see what I mean?

If I’m reading that doc correctly (unlikely), instead of

function CalculateAimPosition (targetPos : Vector3) {

I should have

function CalculateAimPosition (targetPos : Vector3, somethingHere : Vector3.up) {

Doesn’t really give much info does it?

It also says upwards The vector that defines in which direction up is.

desiredRotation = Quaternion.LookRotation(aimPoint,whateverDirectionYouWantUpToBe);


desiredRotation = Quaternion.LookRotation(aimPoint,Vector3.up); //this should be the same as leaving that parameter off


desiredRotation = Quaternion.LookRotation(aimPoint,Vector3.down); //this should be the same as leaving that parameter off


desiredRotation = Quaternion.LookRotation(aimPoint,tank.up); //assuming tank is a Transform this would use whatever is up from the tank once the tank's rotation is taken into account

But I’m going to repeat, this was just one idea, it might be irrelevant to your particular problem in this case.It is just as likely that you need to rotate the model of the turret in the modeling software or something.

I’ve rotated the model six ways to Sunday. Using

desiredRotation = Quaternion.LookRotation(aimPoint,transform.up);

at least gets the turret to track properly (or close to it), but it still rotates the thing over on it’s side (90 degrees on the z-axis)

Fixed the rotation problem (took some finagling in Blender but got it done). Only remaining issue: tracking. The turret now
A) Detects the target collider (via collision sphere as trigger on turret)
B) Orients on the target without rotation funk.

The problem is, the turret stays at that rotation - even if the target moves - until sensing a different target object. Ideally it should adjust aim to maintain a track on the target object - probably done in Update or (possibly OnTriggerStay?).

Current iteration of the code:

#pragma strict

var turnSpeed : float = 5f;
var myTarget : Transform;
var turret : Transform;
private var	desiredRotation : Quaternion;

function Start () {
}

function Update () {
	if (myTarget) {
		CalculateAimPosition (myTarget.position);
		turret.rotation = Quaternion.Lerp(turret.rotation, desiredRotation, Time.deltaTime*turnSpeed);
	}
}
			
function OnTriggerStay (other : Collider) {
	if (other.gameObject.tag == "EnemyArmor") {
		myTarget = other.gameObject.transform;
	}
}

function CalculateAimPosition (targetPos : Vector3) {
	var aimPoint = Vector3 (targetPos.x, turret.position.y, targetPos.z);
	desiredRotation = Quaternion.LookRotation(aimPoint);
}

I would throw some Debug.Log()s in there, try to verify whether myTarget is non-null or not. It doesn’t seem to me it should be null, but any time there’s a bug it means something you believe is wrong…

The only thing that jumps out at me is I still think there’s something wrong with using myTarget.position directly and not myTarget.position relative to turret.position.

Try this.

var aimPoint = Vector3 (targetPos.x-turret.position.x, 0, targetPos.z-turret.position.z);

    desiredRotation = Quaternion.LookRotation(aimPoint);