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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

Cycle locked on targets

Discussion in 'Scripting' started by Panchos, Aug 15, 2015.

  1. Panchos

    Panchos

    Joined:
    Oct 31, 2012
    Posts:
    34
    I need to add a target cycle in my game. I know how I want it to work but I'm struggling to figure the easiest and most logical way to do it.

    It's a 2D top down game, and how it works is you press a button to LOCK ON. This sets a target on the nearest enemy. If then you press the right stick in any direction, it'll move to the next nearest enemy in that direction, RELATIVE to where the current target is.

    I've currently got it locking onto the nearest, but kinda stumped now. Any ideas on how to logically add in the target cycle?
    Thanks
     
  2. Duugu

    Duugu

    Joined:
    May 23, 2015
    Posts:
    241
    Yes. Iterate over all targets and calculate its angle to the player. Then just compare the current targets angle with all other angles and take the target having closest angle.
     
  3. Panchos

    Panchos

    Joined:
    Oct 31, 2012
    Posts:
    34
    Thanks for the tip! I'm pretty new to Unity so if I understand correctly you're saying do this;

    1. Convert the direction pressed on right analogue stick into an angle.
    2. iterate through all the targets, tracking their angle and distance from the current LOCK ON object
    3. find the target with the closest distance AND angle and make that the new LOCKED ON one
     
  4. Ted-Chirvasiu

    Ted-Chirvasiu

    Joined:
    Sep 7, 2010
    Posts:
    381
    That should be it. However, you'll have to decide whether distance is more important than the relative angle or if the angle matters more. Or make some sort of "score", like say score = Mathf.Abs(relativeAngle)/180f+distance/100.0f and pick the lowest. An enemy could be really close to the current locked target, but in the completely opposite direction of the stick while another could be a bit further away but on a much closer relative angle.

    Edit : That would be a very crappy scoring formula where the angle should be normalized and 100.0 would be some sort of maximum possible distance so the dist is normalized as well. And the score would have values between 0 and 2, 0 being the best (but it would not be possible since the the next target would have to sit right on top of the current target)

    Edit 2: Take a look at http://docs.unity3d.com/ScriptReference/Vector2.Angle.html for calculating the angle between your input direction and the direction between the current locked target and the next target.

    float angle = Vector2.Angle(stickAngle, nextPossibleTarget-currentTarget)

    That should do it, I don't think there's any need to normalize the second vector.
     
    Last edited: Aug 15, 2015
  5. Panchos

    Panchos

    Joined:
    Oct 31, 2012
    Posts:
    34
    Thanks for that info, very helpful. However I started putting it into code but I'm getting errors;

    This is part of the find nearest target to current target;
    Code (csharp):
    1. //convert stick to angle
    2. stick_angle = Mathf.Atan2(Input.GetAxis ("Horizontal"), Input.GetAxis ("Vertical")) * Mathf.Rad2Deg;
    3.  
    4. GameObject closest = null;
    5. float distance = Mathf.Infinity;
    6. Vector3 position = lock_on_obj.transform.position;
    7.  
    8. // loop through
    9. foreach (GameObject enemy in all_enemies) {
    10.    Vector3 diff = enemy.transform.position - position;
    11.    float curDistance = diff.sqrMagnitude;
    12.    float angle = Vector2.Angle(stick_angle, position); <----- error on this line 'overload method'
    13.    float score = Mathf.Abs(angle)/180f + curDistance / 100.0f;
    14.  
    15.     // then do the check for lowest score below
    16. }
     
  6. Duugu

    Duugu

    Joined:
    May 23, 2015
    Posts:
    241
    Vector2.Angle accepts two vectors only.
    Code (CSharp):
    1. public static float Angle(Vector2 from, Vector2 to);
    You're passing a float value (stick_angle) and a vector (Vector3 Position) to it.That's what the error is complaining about.

    The correct use would be
    Code (CSharp):
    1. float angle = Vector2.Angle(Position, diff);
    or something like this.
     
  7. Panchos

    Panchos

    Joined:
    Oct 31, 2012
    Posts:
    34
    Thanks. It runs now, although it doesn't work as expected. I'm not factoring in the stick direction anywhere I don't think.
     
  8. Ted-Chirvasiu

    Ted-Chirvasiu

    Joined:
    Sep 7, 2010
    Posts:
    381
    Yes, my bad for not being explicit enough. By stickAngle i meant the Vector2 direction given by your input.

    Code (CSharp):
    1. Vector2 stickDir = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")); //No need to normalize it
    2.  
    3. float angle = Vector2.Angle(stickDir, nextPossibleLock-currentLock);
    Watch out for negative values! Not sure if Vector2.Angle gives absolute values ranging between 0 and 360, 0 and 180 or -180 and 180... If it happens to be 0 to 360, substract 180 and get the absolute value to make it 0 to 180

    Code (CSharp):
    1.  
    2. //get stick direction
    3. stick_dir = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")); //No need to normalize it
    4.  
    5. GameObject closest = null;
    6. Vector3 position = lock_on_obj.transform.position;
    7. float bestScore = Mathf.Infinity;
    8.  
    9. // loop through
    10. foreach (GameObject enemy in all_enemies)
    11.    if(enemy == lock_on_obj){continue;} //Make sure you don't check the current lock-on itself
    12.  
    13.    Vector3 diff = enemy.transform.position - position;
    14.    float curDistance = diff.sqrMagnitude;
    15.  
    16.    float angle = Vector2.Angle(stick_dir, diff);
    17.  
    18.    float score = Mathf.Abs(angle)/180f + curDistance / 100.0f; //Watch out, because you're using squared distance. So the max value (100 in this case) should be squared as well... You're checking for a max distance of 10, not 100 in this case
    19.  
    20.    if(score < bestScore){
    21.        closest = enemy;
    22.    }
    23. }
    Hope it works :)

    Edit : About that scoring value : Technically, given that the real max distance right now is 10 meters, an enemy sitting 5 meters away from the current target at a 0 degrees angle would be just as important as a an enemy sitting right next to the current locked target but at a 90 degrees angle.

    If you would like to add more emphasis on the angle than distance for instance, you could do a weighted average :

    Code (CSharp):
    1. float angleWeight = 2.0f;
    2. float distanceWeight = 1.0f;
    3.  
    4. float score = (Mathf.Abs(angle)/180f*angleWeight + curDistance / 100.0f*distanceWeight) / (angleWeight+distanceWeight);

    This way, the angle value is more important than the distance value. Or you can just tweak that max distance value.
     
    Last edited: Aug 16, 2015
  9. Panchos

    Panchos

    Joined:
    Oct 31, 2012
    Posts:
    34
    Thanks! Finally getting there.

    I can now scroll targets, although it often completely skips over a target if they're not exactly snapped to the same axis as the current target. I don't need the range to be so strict and would favor distance, so would it be the distanceWeight value to tamper with? (I did try but without much luck)

    As an eg. I'm looking to have the ranges like this (8 directions, so the closest enemy in that area will be chosen)
    angle.gif

    EDIT: Sorry, should have called the red center 'CURRENT TARGET' instead of player
     
    Last edited: Aug 16, 2015
  10. Ted-Chirvasiu

    Ted-Chirvasiu

    Joined:
    Sep 7, 2010
    Posts:
    381
    Yes, it would be increasing the distance weight or simply set the 100 from curDistance/100.0f to something else. Again, curDistance is actually the squared distance, so your max distance would actually be 10 units. If most of the times your enemies are way closer to each other, that curDistance/100.0f would give results way below 1.0f, thus making distance always matter less. Say your enemies are 20 units away, that would already be 20*20/100.0f = 4. You might actually want to use a non-squared distance rather than a squared distance for accurately calculating the score, as the result from that division would be squared too. There you should have a 2, not a 4.
    Try "float curDistance = diff.magnitude;" instead and tweak that 100.0f to an average distance between your enemies. Again, i do not know how large your scene is and how far apart they are.
    Also, make sure the angle is between 0 and 180, that could be causing problems too otherwise.

    If tweaked right, it should give a pretty good result.


    Edit : Are you using the keyboard arrows, not a controller?
     
    Last edited: Aug 16, 2015
  11. Panchos

    Panchos

    Joined:
    Oct 31, 2012
    Posts:
    34
    Ah gotcha. My scene is quite small- enemies being as close as 0.1 to each other and from the player

    Thanks again, will play around with it more.
     
    Ted-Chirvasiu likes this.
  12. Ted-Chirvasiu

    Ted-Chirvasiu

    Joined:
    Sep 7, 2010
    Posts:
    381
    Actually, it is more important how far away they are from each other. But yeah, a value of a few units should be good then.
    Also, chunking the search into sectors as show in the picture might lead to some problems. Say you only have 1 enemy left in the game, but he's in the complete opposite direction of what the player pressed. If you would like to be able to select him too, you'd have to perform a search in all sectors.

    Anyway, the final code should be something like :

    Code (CSharp):
    1. //get stick direction
    2. stick_dir = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")); //No need to normalize it
    3. GameObject closest = null;
    4. Vector3 position = lock_on_obj.transform.position;
    5. float bestScore = Mathf.Infinity;
    6. float averageDistance = 5.0f;      //Tweak this
    7.  
    8. // loop through
    9. foreach (GameObject enemy in all_enemies)
    10.    if(enemy == lock_on_obj){continue;} //Make sure you don't check the current lock-on itself
    11.  
    12.    Vector3 diff = enemy.transform.position - position;
    13.    float curDistance = diff.magnitude;
    14.    float angle = Vector2.Angle(stick_dir, diff);
    15.    float score = Mathf.Abs(angle)/180f + curDistance / averageDistance;
    16.  
    17.    if(score < bestScore){
    18.        closest = enemy;
    19.        bestScore = score;   //This!!
    20.    }
    21. }
    You can add the weights too if you want, but tweaking the averageDistance should lead to the same result.
    Good luck!

    Edit : Ah, hold on! Also, don't forget to set the bestScore to score! Sorry about that
     
    LiberLogic969 and Panchos like this.
  13. Panchos

    Panchos

    Joined:
    Oct 31, 2012
    Posts:
    34
    Works really well! Thanks for help :D