Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Voting for the Unity Awards are OPEN! We’re looking to celebrate creators across games, industry, film, and many more categories. Cast your vote now for all categories
    Dismiss Notice
  3. Dismiss Notice

how to make 2 seperate axis turret?

Discussion in 'Scripting' started by ZeBrassFox, Mar 11, 2018.

  1. ZeBrassFox

    ZeBrassFox

    Joined:
    Feb 15, 2018
    Posts:
    41
    Hi again.
    I have various turrets that only function along 1 axis. I wanted to make them work on rotation and also elevation but when I do it the barrel has elevation but doesn't move with the turret. Any help?
    upload_2018-3-11_21-28-49.png
    the code is:
    Code (CSharp):
    1.  
    2. using System.Collections;
    3. using UnityEngine;
    4.  
    5. public class TankTurretExp : MonoBehaviour
    6. {
    7.  
    8.  
    9.     private Transform target;
    10.     [Header("Infomation About turret")]
    11.  
    12.     public int strayFactor = 1;
    13.     public float Health = 100f;
    14.     public float ammo = 500f;
    15.     public float range = 25f;
    16.     public float turnSpeed = 10f;
    17.     public float power = 30f;
    18.     public float fireRate = 7f;
    19.     public float fireCountdown = 0f;
    20.     bool GunnerDeBuff = false;
    21.    
    22.  
    23.     [Header("Target (Land or Air)")]
    24.  
    25.     public string enemyTag = "GroundEnemy";
    26.  
    27.  
    28.  
    29.     [Header("Engine-Required Components")]
    30.  
    31.     public Transform partToRotate;
    32.     public Transform YRotation;
    33.     public GameObject bulletPrefab;
    34.     public Transform firepoint;
    35.  
    36.     public GameObject gunner;
    37.  
    38.     private AudioSource Audio;
    39.  
    40.  
    41.  
    42.     // Use this for initialization
    43.     void Start()
    44.     {
    45.         Audio = GetComponent<AudioSource>();
    46.  
    47.         InvokeRepeating("UpdateTarget", 0f, 0.5f);
    48.     }
    49.  
    50.     void UpdateTarget()
    51.     {
    52.         GameObject[] enemies = GameObject.FindGameObjectsWithTag(enemyTag);
    53.         float shortestDistance = Mathf.Infinity;
    54.         GameObject nearestEnemy = null;
    55.  
    56.         if (gunner == null && GunnerDeBuff == false)
    57.         {
    58.             GunnerDeBuff = true;
    59.             turnSpeed = (turnSpeed / 2);
    60.             fireRate = (fireRate / 2);
    61.         }
    62.  
    63.         foreach (GameObject enemy in enemies)
    64.         {
    65.             float distanceToEnemy = Vector3.Distance(transform.position, enemy.transform.position);
    66.             if (distanceToEnemy < shortestDistance)
    67.             {
    68.                 shortestDistance = distanceToEnemy;
    69.                 nearestEnemy = enemy;
    70.             }
    71.         }
    72.  
    73.         if (nearestEnemy != null && shortestDistance <= range)
    74.         {
    75.             target = nearestEnemy.transform;
    76.         }
    77.         else
    78.         {
    79.             target = null;
    80.         }
    81.  
    82.     }
    83.  
    84.  
    85.     void Update()
    86.     {
    87.         if (target == null)
    88.             return;
    89.  
    90.         //Rotating to lock on
    91.         Vector3 dir = target.position - transform.position;
    92.         Quaternion lookRotation = Quaternion.LookRotation(dir);
    93.         Vector3 rotation = Quaternion.Lerp(partToRotate.rotation, lookRotation, Time.deltaTime * turnSpeed).eulerAngles;
    94.         partToRotate.rotation = Quaternion.Euler(0f, rotation.y, 0f);
    95.  
    96.         Vector3 dir2 = target.position - transform.position;
    97.         Quaternion lookRotation2 = Quaternion.LookRotation(dir2);
    98.         Vector3 rotation2 = Quaternion.Lerp(YRotation.rotation, lookRotation, Time.deltaTime * turnSpeed).eulerAngles;
    99.         YRotation.rotation = Quaternion.Euler(0f, rotation2.z, 0f);
    100.  
    101.  
    102.         if (fireCountdown <= 0f)
    103.         {
    104.             if (ammo > 0)
    105.             {
    106.                 ammo = ammo - 1;
    107.                 Shoot();
    108.                 fireCountdown = 1f / fireRate;
    109.             }
    110.             else
    111.             {
    112.                 StartReload();
    113.             }
    114.         }
    115.  
    116.         fireCountdown -= Time.deltaTime;
    117.  
    118.     }
    119.  
    120.  
    121.  
    122.     void Shoot()
    123.     {
    124.  
    125.         Audio.Play();
    126.         GetComponent<Animation>().Play();
    127.         var bullet = Instantiate(bulletPrefab, firepoint.position, firepoint.rotation);
    128.  
    129.         var randomNumberX = Random.Range(-strayFactor, strayFactor);
    130.         var randomNumberY = Random.Range(-strayFactor, strayFactor);
    131.         var randomNumberZ = Random.Range(-strayFactor, strayFactor);
    132.         bullet.transform.Rotate(randomNumberX, randomNumberY, randomNumberZ);
    133.         bullet.GetComponent<Rigidbody>().velocity = bullet.transform.forward * power;
    134.         Destroy(bullet, 2.0f);
    135.     }
    136.  
    137.  
    138.  
    139.  
    140.     public void StartReload()
    141.     {
    142.         ammo = 250;
    143.     }
    144.  
    145.  
    146.  
    147.  
    148.  
    149. }
    150.  
     
  2. fire7side

    fire7side

    Joined:
    Oct 15, 2012
    Posts:
    1,819
    I didn't read your code, but it seems like if the cannon is a child of the turret, and you use transform.localRotation, it should both turn with the turret and turn on it's own.
     
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,599
    The general way to do this "right" is to set up a root GameObject that is the portion of the turret that does NOT turn.

    Under this put a blank GameObject and call it "headingPivot". This is the GameObject that you will rotate ONLY in the Y direction, for heading changes of the turret.

    Under this "headingPivot" put another GameObject and call it "elevationPivot". This is the GameObject that you will rotate ONLY in the local X direction, for elevation changes to the turret.

    I am going to call the above GameObjects your "bones," as it is the same concept with animation and skinned meshes.

    Now that you have created these three GameObjects "bones" parented like this, go and get your visual GameObjects, ie., your meshes, and "hang them" on the right part of the above three, without ever changing the bones. Only change the visual meshes until they are right.

    If you do need to change the bones a bit (such as to raise the pivot point of the elevationPivot), only slide it directly up the Y axis, so that it now pivots higher. Do not move it laterally or rotate it!

    Finally, your script should only ever move the bones, never the visual parts. The visual parts will move automatically as children of the bones. And yes, for each of them you will be setting the transform.localRotation only, never the transform.rotation, as @fire7side said above.
     
  4. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    I also didn't really read your code, but this is a question that comes up semi-regularly, I think.

    I'm not sure how to do it with localRotation, but I think this is working..

    Where 'parentObj' rotates on y-axis, and childObj on the 'x axis'.

    Edit: Had a few mistakes, missed some checks, and was using 'transform.position/up' instead of parentObj (because I had written the script on the parentObj*). Hopefully this updated code is sound. :)
    Code (csharp):
    1.  
    2. Vector3 t = Vector3.ProjectOnPlane(target.position - parentObj.position, parentObj.up);
    3. if(t != Vector3.zero)
    4.    parentObj.rotation = Quaternion.LookRotation(t);
    5.  
    6. t = target.position - childObj.position;
    7. if(t != Vector3.zero)
    8.    childObj.rotation = Quaternion.LookRotation(target.position - childObj.position);
     
    Last edited: Mar 12, 2018
  5. ZeBrassFox

    ZeBrassFox

    Joined:
    Feb 15, 2018
    Posts:
    41
    Hi again, Thanks for all the responses. Unfortunately Im a real noob when it comes to C#, how would I implement LocalRotation into my code? dir controls the heading while dir2 control elevation. Cheers
     
  6. ZeBrassFox

    ZeBrassFox

    Joined:
    Feb 15, 2018
    Posts:
    41
    You are an absolute legend. you all are, thanks so much for the help!
     
  7. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Ya, it should be noted that that's not perfect. I was actually working on a 'clamped angle' addition, but got distracted and never finished.

    Anyhow, glad I could help a bit , along with the others. :)
     
  8. ZeBrassFox

    ZeBrassFox

    Joined:
    Feb 15, 2018
    Posts:
    41
    :)
    1 more thing, how would I limit the angles? wanting to put turrets in sponsons like the ww1 male tank but don't want them to phase into the tank when trying to look at certain enemies.
    Cheers!
     
  9. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,599
    You can limit the angle trivially with min and maximum values that you clamp it to.

    But if you want to limit the angle variably, such as having more variability in the front and less in the back, one way to do that is to make a pair of AnimationCurve objects (AnimationCurves are nothing to do with animation: they are just a mathematical function with keyframe pairs throughout its length) that map from 0 to 360 degrees and show what the limits of the turret are, and then as the traverse (heading) sweeps, you would enforce different elevation limits at different points on the compass.

    Also, just wanna put in a shout-out for the Asset Store guy "Inspond..." I bought some of his great tank packs and they really look good and work well. Check 'em out.
     
  10. LeftyRighty

    LeftyRighty

    Joined:
    Nov 2, 2012
    Posts:
    5,148
    Out of curiosity does this work with any orientation of the parent? i.e if the thing the turret is on inverts/flips over? seem to remember seeing attempts at answering this question in the past getting really messy if the parent isn't "upwards" orientated.
     
  11. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,599
    Check my post above. Make the bones all "upwards" oriented, then you can hang the visuals off them anyway you like, all twisted and hamajang and everything, as long as the only thing you drive are the bones.
     
  12. ZeBrassFox

    ZeBrassFox

    Joined:
    Feb 15, 2018
    Posts:
    41
    Kurt-Dekker, sorry to be a pain,
    how would I add the clamp method into my the code?
    Code (CSharp):
    1.     Vector3 t = Vector3.ProjectOnPlane(target.position - partToRotate.position, partToRotate.up);
    2.         if (t != Vector3.zero)
    3.             partToRotate.rotation = Quaternion.LookRotation(t);
    4.  
    5.         t = target.position - YRotation.position;
    6.         if (t != Vector3.zero)
    7.             YRotation.rotation = Quaternion.LookRotation(target.position - YRotation.position);
    8.  
    Cheers :)
     
  13. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    That is actually what hangs me up, sometimes, when writing these answers.. my mind wanders there and I try to solve that. I thought I had solved it once in the past, but now I'm not sure, and I deleted the code (as I usually do when writing for the forums*).
    - So, I'm not so sure that works all the time..

    Perhaps I will try to work it out, again, someday. :) Along with clamping the values, which is probably also desired, usually.
     
  14. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,599
    No, good question. I'll let the author of that codelet answer though. My approach would be to use a float to drive and indicate heading/elevation, so without compiling and testing the above code (I'm not able to do that), I really can't comment on how to clamp it.

    If it were my way it would just be an if statement comparing the float to min/max values.
     
  15. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    There is probably more than 1 way to answer this, and I do not have much time to test it out right now..

    You could try @Kurt-Dekker 's answer instead of mine, if you'd like. :) I only posted as another option the bit of code.

    When I had been working on it before (the clamping portion), I believe I added a third parent game object that never moved. I would compare its forward to the new (desired angles). I was pretty tired, though, when I was working on it.
     
  16. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    I'd love to know if this solution is good/decent, and keep it recorded for future reference :)
    This solution sounds a lot more like the suggested above than my small code, but since no code was posted I don't know if the execution is the same or not.

    I also think/hope/wonder? if this will work for any rotation of the parent; I've done some tests, and it looks okay, but who knows..

    Code (csharp):
    1. public class Test10 : MonoBehaviour {
    2.  
    3.     /**
    4.      * Transform layout:
    5.      *
    6.      * Empty, with script
    7.      * Child empty 'yobj'
    8.      * Child of that child, 'xobj'
    9.      * Actual game object.
    10.      *                             **/
    11.     public Transform yobj;
    12.     public Transform xobj;
    13.     public Transform target;
    14.     public float ymin = -30f, ymax = 40f;
    15.     public float xmin = -35f, xmax = 60f;
    16.  
    17.     bool doTarget = false;
    18.     void Update()
    19.     {
    20.         if (Input.GetMouseButtonDown(0))
    21.         {
    22.             doTarget = !doTarget;
    23.         }
    24.         if (doTarget) Retarget();
    25.     }
    26.     void Retarget()
    27.     {
    28.         Vector3 t = Vector3.ProjectOnPlane(target.position - transform.position, transform.up);
    29.  
    30.         float angle = Vector3.SignedAngle(transform.forward, t, transform.up);
    31.         angle = Mathf.Clamp(angle, ymin, ymax);
    32.  
    33.         Quaternion rot = Quaternion.Euler(0, angle, 0);
    34.         yobj.localRotation = rot;    
    35.  
    36.         angle = Vector3.Angle((target.position - xobj.position), yobj.forward);
    37.         angle *= -Mathf.Sign(Vector3.Dot(transform.up, (target.position - xobj.position).normalized));
    38.         angle = Mathf.Clamp(angle, xmin, xmax);
    39.  
    40.         rot = Quaternion.Euler(angle, 0, 0);
    41.         xobj.localRotation = rot;
    42.  
    43.         Debug.DrawRay(yobj.position, yobj.forward * Vector3.Distance(yobj.position, t), Color.green);
    44.         Debug.DrawRay(xobj.position, xobj.forward * Vector3.Distance(xobj.position, target.position), Color.blue);
    45.     }
    46. }
     
  17. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    897
    this is the gist of how I clamp a rotation. its not Euler-dependent so its safe from gimbal-locking

    Code (CSharp):
    1. public Quaternion ClampRotation(Quaternion current, Vector3 directionConstraint, float angle, bool clampInstantly, float maxDegreeDelta)
    2.         {
    3.             Quaternion constraint = Quaternion.LookRotation(directionConstraint);
    4.             float angleWrapped = Mathf.PingPong(angle, 180);      // this ensures that angles not between 0-180 wrap properly
    5.             float dot = Mathf.Acos(angleWrapped * Mathf.Deg2Rad); // calc target Dot product from provided angle
    6.  
    7.  
    8.             // if current deviation is within threshold, do nothing and return current rotation as-is
    9.             if (Quaternion.Dot(current, constraint) >= dot)
    10.                 return current;
    11.  
    12.             // otherwise find where we need to rotate to to make it within constrained area
    13.             Quaternion targetRotation = Quaternion.RotateTowards(constraint, current, angleWrapped);
    14.  
    15.             if (clampInstantly)
    16.                 return targetRotation;
    17.  
    18.             //otherwise rotate current towards targetRotation
    19.             return Quaternion.RotateTowards(current, targetRotation, maxDegreeDelta);
    20.  
    21.         }
    this is for rotations clamped within 180 degrees of a look constraint. A larger clamp would also require a revolutions value to be tracked as well
     
  18. ZeBrassFox

    ZeBrassFox

    Joined:
    Feb 15, 2018
    Posts:
    41
    Thanks for the help but I cant seem to get it to work
     
  19. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Which one or both (neither*)?