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.
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.
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
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.
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.
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);
}
}
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?
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.
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);