Search Unity

Coding unit movement - best practices?

Discussion in 'Scripting' started by Desprez, Sep 9, 2012.

  1. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    For my first practice project, I'm doing RTS/Defense style game.

    But before I get any further along, I want to know if I'm going about coding the units in the best manner.
    What I have is working so far, but I'm new to Unity, and I'd like some advice on if I'm going about this in a way that won't cause problems later, or if I'm making things harder than they need to be, etc.

    I'm not looking for code optimization at the moment, per se. (though such suggestions are welcome) But rather, if my overall approach is appropriate so far.

    Anyway, here's a brief set up:
    I have some vehicles. The vehicles have turrets.
    For testing purposes, I can click on a unit to select it. While selected, I can click on another unit and that will set the target for the currently selected unit. It will then chase the unit down while keeping it's turret aimed at the target.

    Relevant components:
    The units are rigidbodies so they can crash and stuff.
    They have a projector to indicate if they are selected.
    Script: Unit_Interaction script (so they player can select and target by clicking)
    Script: Unit_Controler (Controls the vehicle movement, and keeps track of waypoints, targets, and general AI)
    Script: Turret_Controler (Controls the turret(s) with target objects passed from the Unit_Controler script)

    I've tried to comment the code as best I can.

    Unit_Controler:
    Code (csharp):
    1.  
    2. #pragma strict
    3.  
    4. var turnrate : float; //vehicle turn rate
    5. var accell : float; //vehicle accelleration
    6. var topspeed : float;
    7. var target : GameObject; //current target
    8. var turrettarget : GameObject; //current turret target
    9. var curWPT : Vector3; //current waypoint location (not used yet)
    10.  
    11. private var rotation : Quaternion;
    12. private var speed : float; //current speed
    13. private var curtopspeed : float; //current top speed (changes based on distance to target for smarter accelleration)
    14. var hasWPT : boolean; //do I have any destination at all?
    15. hasWPT = true;
    16. accell *= .1; //accell is multiplied by .1 for actual value. The larger number are easier to handle when designing the vehicles
    17. var destination : Vector3; //current destination, might be a waypoint, might be a target location
    18.  
    19. function Update () {
    20.     if(hasWPT == true) { //early out if no destination
    21.         destination = target.transform.position; //destination is currently hardcoded as the current target
    22.        
    23.         //set speed
    24.         var distance = Vector3.Distance(destination, transform.position); //find distance to destination location
    25.         if(distance > 10) { //is destination further than 10 meters away?
    26.             speed += accell; //increase speed by accelleration
    27.             //smart topspeed
    28.             if(distance <= topspeed) { //is the destination distance less than top speed as measured in distance?
    29.                 curtopspeed = topspeed * distance/topspeed; //current tops speed will be less than normal topspeed. The closer the destination the lower current top speed will be
    30.             } else { //destination is suffiently far enough away to move at max speed
    31.                 curtopspeed = topspeed; //set current top speed to top speed (max)
    32.             }
    33.             //
    34.             if(speed > curtopspeed) {speed = curtopspeed;} //make sure current speed isn't above current top speed. Set to current top speed if neccessary
    35.             if(speed < (curtopspeed*-1) / 2) {speed = (curtopspeed*-1) / 2;} //if moving in reverse, current top speed is half normal (reverse not supported yet)
    36.         } else { //destination is within 10 meters
    37.             //slow down and stop
    38.             speed -= accell * 2; //reduce speed (decelleration is double the accelleration rate)
    39.             if(speed < 0) {speed = 0;} //if speed reduces past 0, set to 0. (moving in reverse not supported yet)
    40.         }
    41.         //turn
    42.         rotation = Quaternion.LookRotation(destination - transform.position); //turning required to face destination
    43.         transform.rotation = Quaternion.RotateTowards(transform.rotation, rotation, turnrate * Time.deltaTime); //do vehicle rotation
    44.         transform.localEulerAngles.x = 0; //keep vehicle level
    45.         transform.localEulerAngles.z = 0;
    46.         transform.Translate(0, 0, speed * Time.deltaTime); //do new vehicle location
    47.         //I have to add some code that detects if a unit's wheeles are touching the ground.
    48.         //If they aren't, then make it so it can't change it's own rotation and position - let physics do it.
    49.     }
    50. }
    51.  
    Turret_Controler:
    Code (csharp):
    1.  
    2. #pragma strict
    3.  
    4. var aimspeed : float; //turret rotation speed
    5. var ylimit : float; //turret rotation limit from straight ahead
    6. var xlimit : float; //turret elevation limit
    7.  
    8. var turret : GameObject; //object that rotates
    9. var gun : GameObject; //object that elevates
    10. private var rotation : Quaternion;
    11. private var elevation : Quaternion;
    12.  
    13. function Update () {
    14.     var mytarget = GetComponent(Unit_Controler).turrettarget;
    15.  
    16.     if(mytarget != null) { //rotation required to face target
    17.         rotation = Quaternion.LookRotation(mytarget.transform.position - turret.transform.position);
    18.         elevation = Quaternion.LookRotation(mytarget.transform.position - gun.transform.position);
    19.     } else { //rotation required to return to forward facing
    20.         rotation = Quaternion.LookRotation(transform.position - turret.transform.position);
    21.         elevation = Quaternion.identity;
    22.     }
    23.     //rotate the turret
    24.     turret.transform.rotation = Quaternion.RotateTowards(turret.transform.rotation, rotation, aimspeed * Time.deltaTime);
    25.     turret.transform.localEulerAngles.x = 0; //keep other turret axes level
    26.     turret.transform.localEulerAngles.z = 0;
    27.     //elevate the gun
    28.     gun.transform.rotation = Quaternion.RotateTowards(gun.transform.rotation, elevation, aimspeed * Time.deltaTime);
    29.     gun.transform.localEulerAngles.y = 0; //keep other gun axes level
    30.     gun.transform.localEulerAngles.z = 0;
    31.  
    32.     if(ylimit != 0) { //0 = no rotation constraint limit
    33.         if(turret.transform.localEulerAngles.y > ylimit  turret.transform.localEulerAngles.y <= 180) { //check if rotation is above limit and targert is on that side of me
    34.             turret.transform.localEulerAngles.y = ylimit; //set to limit
    35.         } else if(turret.transform.localEulerAngles.y < 360-ylimit  turret.transform.localEulerAngles.y > 180) { //check if below, etc.
    36.             turret.transform.localEulerAngles.y = 360-ylimit; //set to limit
    37.         }
    38.     }
    39.     //hardcode depression limit at 4 deg down, for now
    40.     //positive numbers are down, negative numbers are up
    41.     if(gun.transform.localEulerAngles.x > 4  gun.transform.localEulerAngles.x <= 180) { //check if elevation is past limit, etc
    42.         gun.transform.localEulerAngles.x = 4; //set to limit
    43.     }
    44.     if(xlimit != 0) { //0 = no limit
    45.         if(gun.transform.localEulerAngles.x < 360-xlimit  gun.transform.localEulerAngles.x > 180) { //check elevation limit, etc
    46.             gun.transform.localEulerAngles.x = 360-xlimit; //set to limit
    47.         }
    48.     }
    49. }
    50.  
     
  2. KyleOlsen

    KyleOlsen

    Joined:
    Apr 3, 2012
    Posts:
    237
    If your vehicle has a rigidbody component I would move them with rigidbody.AddForce instead of transform.Translate. Also, again if you're using a rigidbody, handle all things related to physics (movement, rotation, forces) in FixedUpdate. As for the first line in the turret update function, you are constantly getting your Unit_Controler component every frame, rather than just doing:

    Code (csharp):
    1.  
    2. var myUnitControler : Unit_Controler;
    3.  
    4. function Start() {
    5.  myUnitControler = GetComponent(Unit_Controler);
    6. }
    7.  
    8. function Update() {
    9.  myUnitControler.turrettarget;
    10. }
    11.  
    You'll save some processing power this way! Good luck :)

    Also a note on some "best practices". Polymorphism and inheritance provide a great way of creating reusable, clean, efficient code for AI.
     
    Last edited: Sep 9, 2012
  3. baronsamedi

    baronsamedi

    Joined:
    May 31, 2011
    Posts:
    29
    biggest thing i can think of is condense that code down to one single unit manager that itterates through your units list and moves them each fixedUpdate
     
  4. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    Thanks for the input!

    Oh really? Putting the scripts on the objects is bad?
    If I just have a central unit manager, I'm going to end up doing a lot of Find() calls every fixedUpdate to operate the individual parts and units. I was under the impression that Find() calls are relatively slow.
    Unless you're saying that same scrips on each unit is worse than the added Find() calls every frame.

    I guess I can get around that with an array that holds the object references. But then I have to do a lot of fiddling with that array as units spawn and get killed.
     
  5. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    I definitely wouldn't do that. Let the units manage themselves.
     
  6. tomvds

    tomvds

    Joined:
    Oct 10, 2008
    Posts:
    1,028
    No, putting a script that controls a certain object on that particular object is good design. Messing up this good design just to save a few function calls is among the worst possible premature optimization you can do.
     
  7. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    On a separate note, upon reflection, I might not need RigidBodies for this game. I want units to be able to follow terrain contours, but accurate collisions, weapon recoil, and impact forces are just a bonus. The main thing is the terrain is followed and that units don't pass through each other.

    So, will RigidBody physics be overkill in the sense that it will be much more efficient to write code to follow terrain?
    I don't know how much processing overhead the more accurate physics will add.

    I still need to learn the RigidBody physics for other projects, though.
     
  8. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    I do it by Raycasting the terrain underneath each unit and adjusting based on the hit point.
     
  9. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    Ok, but how do you find the angle of terrain at that particular point?

    Also, looking at the docs, it looks like colliders need rigidBodies to actually collide with objects. How do I make collisions if I remove rigidBodies? By checking to see if a collision exists and the reduce translation to 0 if true?

    EDIT: Oh it doesn't look like and collision triggers are sent upon two static colliders colliding.
     
    Last edited: Sep 13, 2012
  10. chilton

    chilton

    Joined:
    May 6, 2008
    Posts:
    564
    Either go with rigidbodies and addForce or do ray casting and use translate/rotate as necessary.
    Doing a translate along with a rigidbody will result in crap performance (by comparison) and lots of odd glitches.
     
  11. Rico21745

    Rico21745

    Joined:
    Apr 25, 2012
    Posts:
    409
  12. Desprez

    Desprez

    Joined:
    Aug 31, 2012
    Posts:
    305
    I'm definitely going to have to take a look at that.
     
  13. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    Because it can sometimes be faster to use your own.

    http://docs.unity3d.com/Documentation/ScriptReference/RaycastHit-normal.html
     
  14. baronsamedi

    baronsamedi

    Joined:
    May 31, 2011
    Posts:
    29
    im no expert but i was under the impression that a single unit manager script was kinda the norm for games with lots of units... its not that hard to manage, just add the unit to a list on creation and remove when its destroyed. and from the (limited) testing that i did, there was a huge diference in having one script controlling all movements instead of each unit having its own movement script. thats not to say each unit doesnt have a script attached, that holds info for that specific unit like waypoints hp etc. You wont need find just getcomponent to access the script for the unit


    http://forum.unity3d.com/threads/100041-Performance-problem-with-modifying-transform.position
     
  15. KelsoMRK

    KelsoMRK

    Joined:
    Jul 18, 2010
    Posts:
    5,539
    There's a few things to consider in regards to that. One - caching your transform will net you performance gains and Two - not using Update will as well. I use a state machine that moves each unit (that is currently in a move state) via a coroutine. This way the engine handles a list of currently running coroutines instead of me managing a list of units via Update. When the unit gets to his destination he switches to an idle state, the move coroutine completes and is cleaned up accordingly. Works great.
     
  16. CrazySi

    CrazySi

    Joined:
    Jun 23, 2011
    Posts:
    538
    LOL @ Unity and Polymorphism + inheritance. Nicely recited parrot fashion though. :)