Search Unity

[help] Ai damage & attack

Discussion in 'Scripting' started by gauty40, Jul 26, 2014.

  1. gauty40

    gauty40

    Joined:
    Feb 22, 2014
    Posts:
    15
    ive tried multiple times to add the ability for my ai (a zombie) to attack and to be able to recieve damage and die when it's health hits zero, below is my ai script allowing it to use a navmesh to track and follow the player and play animations (walking, running, attacking, etc), any help would be vastly appreciated as i despise working with AI and i dont want it to cause the end of my game -.-

    vartarget : Transform;
    varisChasing : boolean = true;
    varisAttacking : boolean = false;
    varNavComponent : NavMeshAgent;
    varthisObject : Transform;
    varstopDistance : int;
    varisInTeleporter : boolean = false;
    varminSpeedWalk : int;
    varmaxSpeedWalk : int;;
    varminSpeedRun : int;
    varmaxSpeedRun : int;
    varminSpeedAttack : int;
    varmaxSpeedAttack : int;
    varisRunner : boolean;
    varcurSpeed : int;
    varmoveAnimName : String;
    varwalkAnimName : String;
    varrunAnimName : String;
    varattackAnimName : String;
    varidleAnimName : String;
    vartimeToAttack : int;

    functionAwake(){
    thisObject = transform;
    }

    functionStart(){
    target = GameObject.FindWithTag("Player").transform;

    NavComponent = thisObject.transform.GetComponent(NavMeshAgent);

    if (isRunner == true){
    curSpeed = Random.Range(minSpeedRun, maxSpeedRun);
    moveAnimName = runAnimName;
    }

    if (isRunner == false){
    curSpeed = Random.Range(minSpeedWalk, maxSpeedWalk);
    moveAnimName = walkAnimName;
    }

    NavComponent.speed = curSpeed;
    }

    functionUpdate(){
    varcurDistance = Vector3.Distance(target.position, thisObject.position);

    NavComponent.stoppingDistance = stopDistance;

    if (curDistance <= stopDistance){
    isChasing = false;
    isAttacking = true;
    }

    if (curDistance > stopDistance){
    isChasing = true;
    isAttacking = false;
    }

    if (isChasing == true){
    NavComponent.SetDestination(target.position);
    NavComponent.Resume();
    thisObject.animation.CrossFade(moveAnimName);
    }

    if (isChasing == false){
    NavComponent.Stop();
    if (isAttacking == false){
    thisObject.animation.CrossFade(idleAnimName);
    }
    }

    if (isAttacking == true){
    target.SendMessage ("PlayerDamage");
    thisObject.animation.CrossFade(attackAnimName);
    waitFunction();
    BroadcastMessage ("StopAttacking");
    }
    }

    functionwaitFunction(){
    yieldWaitForSeconds (timeToAttack);
    }
     
  2. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    Please wrap the code in
    Code (csharp):
    1. code tags
    [ CODE ] but w/o the spaces [ /CODE ]
     
  3. gauty40

    gauty40

    Joined:
    Feb 22, 2014
    Posts:
    15
    Code (csharp):
    1. vartarget : Transform;
    2. varisChasing : boolean = true;
    3. varisAttacking : boolean = false;
    4. varNavComponent : NavMeshAgent;
    5. varthisObject : Transform;
    6. varstopDistance : int;
    7. varisInTeleporter : boolean = false;
    8. varminSpeedWalk : int;
    9. varmaxSpeedWalk : int;;
    10. varminSpeedRun : int;
    11. varmaxSpeedRun : int;
    12. varminSpeedAttack : int;
    13. varmaxSpeedAttack : int;
    14. varisRunner : boolean;
    15. varcurSpeed : int;
    16. varmoveAnimName : String;
    17. varwalkAnimName : String;
    18. varrunAnimName : String;
    19. varattackAnimName : String;
    20. varidleAnimName : String;
    21. vartimeToAttack : int;
    22.  
    23. functionAwake(){
    24. thisObject = transform;
    25. }
    26.  
    27. functionStart(){
    28. target = GameObject.FindWithTag("Player").transform;
    29.  
    30. NavComponent = thisObject.transform.GetComponent(NavMeshAgent);
    31.  
    32. if (isRunner == true){
    33. curSpeed = Random.Range(minSpeedRun, maxSpeedRun);
    34. moveAnimName = runAnimName;
    35. }
    36.  
    37. if (isRunner == false){
    38. curSpeed = Random.Range(minSpeedWalk, maxSpeedWalk);
    39. moveAnimName = walkAnimName;
    40. }
    41.  
    42. NavComponent.speed = curSpeed;
    43. }
    44.  
    45. functionUpdate(){
    46.  
    47. varcurDistance = Vector3.Distance(target.position, thisObject.position);
    48.  
    49. NavComponent.stoppingDistance = stopDistance;
    50.  
    51. if (curDistance <= stopDistance){
    52. isChasing = false;
    53. isAttacking = true;
    54. }
    55.  
    56. if (curDistance > stopDistance){
    57. isChasing = true;
    58. isAttacking = false;
    59. }
    60.  
    61. if (isChasing == true){
    62. NavComponent.SetDestination(target.position);
    63. NavComponent.Resume();
    64. thisObject.animation.CrossFade(moveAnimName);
    65. }
    66.  
    67. if (isChasing == false){
    68. NavComponent.Stop();
    69. if (isAttacking == false){
    70. thisObject.animation.CrossFade(idleAnimName);
    71. }
    72. }
    73.  
    74. if (isAttacking == true){
    75. target.SendMessage ("PlayerDamage");
    76. thisObject.animation.CrossFade(attackAnimName);
    77. waitFunction();
    78. BroadcastMessage ("StopAttacking");
    79. }
    80. }
    81.  
    82. functionwaitFunction(){
    83. yieldWaitForSeconds (timeToAttack);
    84. }
    better?
     
  4. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    Yes. I don't have direct answers for you, but you're doing things wrong, or poorly.

    In your start, "FindWithTag" is slow -- and that means only one thing can be tagged player. If you can't have it pre-set in the inspector, try just Find("Name");

    Up to you, but "if(isRunner ==true)" can be written as "if (isRunner)" and "if(isRunner ==false)" as "if (!isRunner)" (the "!" indicates false.

    SendMessage is a slow way of calling one function on one script. Try target.GetComponent(scriptName).PlayerDamage();

    Same for BroadcastMessage. But what does BroadcastMessage ("StopAttacking"); refer to?

    I suggest you try compartmentalizing your script more. Small functions for each thing you want to do. Add in the complex stuff, like animations, after the basic stuff is working.
     
  5. gauty40

    gauty40

    Joined:
    Feb 22, 2014
    Posts:
    15

    "StopAttacking" was a bit of clutter from before :c
    i tried the target.GetComponent part but all it did was stop the attack animation from playing and nothing else
    after tidying up the script im now getting this error
    "Failed to call function PlayerDamage of class PlayerDamageNew
    Calling function PlayerDamage with no parameters but the function requires 1"
    sorry, i know im awful at scripting ai :/
     
  6. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    Does PlayerDamage() have any parameters? Perhaps:

    Code (csharp):
    1.  
    2. function PlayerDamage(damage : float){
    3.      print ("Player Receives " + damage + " Damage!");
    4. }
    5.  
    if so, then you need to call this:

    Code (csharp):
    1.  
    2. target.GetComponent(playerScript).PlayerDamage(damage);
    3.  
    Where "playerScript" is whatever you've named the script on your target player and damage is the amount of damage player should receive. However, keep in mind you've got this in your Update() loop, so that will happen every frame that isAttacking is true.

    You may find it easier to put that part in a separate function in the same script so you can call it just once outside of the loop, perhaps using an animation event so that it happens at the proper time during the animation.
     
  7. gauty40

    gauty40

    Joined:
    Feb 22, 2014
    Posts:
    15
    thanks for the help, now when i get near the zombie it attacks but like you said it does it in the loop, so i basically get hit a million times in one second and die instantly, ive tried moving the string to a few different places but none seem to stop it killing me instantly :/
     
  8. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    Look into Animation Events. (Google: "Unity3d Animation Event" -- goggling "unity3d" and anything you're looking for usually always works well)

    An animation event is a trigger that happens at the frame you set in the animation. Perhaps at the apex of a slash attack. In this case, you'd set it to the function PlayerDamage() as set above. (Perhaps instead of including a pre-set variable, you'll do some logic in that function to get a damage amount based on attack or defense or what not).

    It will only fire once, and will always fire on that exact frame. That way the player gets the damage (and reacts, I'd think) only when it looks like they got hit.

    WIth this, you also don't have to worry about scripting the attack, all you have to do is turn on the NavMeshAgent for your zombie, and wait for the enemy to be close to the player. Once it's close, trigger the attack animation (which will trigger the damage function).

    If you use a float variable as an attack timer, say:

    Code (csharp):
    1.  var attackTimer  : float  = 5.0;
    2. var attackTime  :  float  = 0.0;
    And in your update() loop: (playerIsClose is a variable you can set up -- either like you have it, or if you have a lot of zombies, try doing it with an overlap sphere from the player, which is faster than a Vector3.Distance from each enemy)

    Code (csharp):
    1. attackTime += Time.deltaTime;
    2. if (playerIsClose && attackTime >= attackTimer)
    3. {
    4.    Animator.SetTrigger("attack");
    5.    attackTimer = 0.0;
    6. }
    This will ensure that the zombie, always chasing the player (because the navmeshagent is still turned on), will attack every 5 seconds.

    You'll also likely want to do a repeating coroutine (function) for resetting the destination for the navmesh.

    In your Start() function use "Invoke Repeating" which will run the function, starting in 0 frames, every 0.5 seconds:

    Code (csharp):
    1. InvokeRepeating("UpdateDestination", 0, 0.5);
    And then include this function which will ensure the zombie is always chasing the players new position, as the player is likely going to move:

    Code (csharp):
    1. function UpdateDestination(){
    2.    NavMeshAgent.SetDestination(target.transform.position);
    3. }
    Just be sure that when you want to stop updating the position (like if the zombie runs away or is too far from the player and moves back to their start position, or dies etc, you call cancel invoke:

    Code (csharp):
    1. CancelInvoke("UpdateDestination");
    Hope all that helps.
     
  9. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    Also, if you can, I suggest you start using Mecanim for your animations. It'll make your life a LOT easier, and you won't have to worry about transitions and all that.
     
  10. gauty40

    gauty40

    Joined:
    Feb 22, 2014
    Posts:
    15
    i cannot use the animation code as im using a zombie from the asset store (read only animations)
    i get an error with the other codes ""UnityEngine.Animator" is required to access non static member "set trigger""
     
  11. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    Yep, the setTrigger is a mecanim thing. In mecanim, you set your animations so allow a transition from "any state" to "Attack" when the "attack" trigger is set. It only triggers the animation once, then will do whatever the exit transition you set is (usually back to idle/walking).

    You should be able to use animation events with any animation. Have you checked the "Animation" window while your zombie is selected? You should see a list of animations. At the top, there's a vertical flag. scrub to the proper place in the animation timeline, and click that to add an event. You'll be able to choose from the functions that are in your zombie scripts, so make sure the function (at least the name) is already set.

    If that method doesn't work, select the model in the project view (not the hierarchy), and click the "Animation" tab of the import settings. You should see all the attached animations. Select the attack one, and scroll down. Above the preview window you'll see an "Events" tab. Use that to add the event. In this method, you have to name the function here.

    Note that if you open the carrot on your zombie, you should see the animation files. You can use those in the mecanim system.
     
  12. gauty40

    gauty40

    Joined:
    Feb 22, 2014
    Posts:
    15
    i have it linked to an animation and setup right but im getting ""Animation" AnimationEvent "PlayerDamage" has no reciever! Are you missing a component?"

    this is the updated code
    Code (csharp):
    1.  
    2. vartarget : Transform;
    3. varisChasing : boolean = true;
    4. varisAttacking : boolean = false;
    5. varNavComponent : NavMeshAgent;
    6. varthisObject : Transform;
    7. varstopDistance : int;
    8. varisInTeleporter : boolean = false;
    9. varminSpeedWalk : int;
    10. varmaxSpeedWalk : int;;
    11. varminSpeedRun : int;
    12. varmaxSpeedRun : int;
    13. varminSpeedAttack : int;
    14. varmaxSpeedAttack : int;
    15. varisRunner : boolean;
    16. varcurSpeed : int;
    17. varmoveAnimName : String;
    18. varwalkAnimName : String;
    19. varrunAnimName : String;
    20. varattackAnimName : String;
    21. varidleAnimName : String;
    22. vartimeToAttack : int;
    23. varmaximumHitPoints = 5.0;
    24. varattackTimer : float = 5.0;
    25. varattackTime : float = 0.0;
    26.  
    27. functionAwake(){
    28. thisObject = transform;
    29. }
    30.  
    31. functionStart(){
    32. target = GameObject.FindWithTag("Player").transform;
    33.  
    34. NavComponent = thisObject.transform.GetComponent(NavMeshAgent);
    35.  
    36. if (isRunner){
    37. curSpeed = Random.Range(minSpeedRun, maxSpeedRun);
    38. moveAnimName = runAnimName;
    39. }
    40.  
    41. if (!isRunner){
    42. curSpeed = Random.Range(minSpeedWalk, maxSpeedWalk);
    43. moveAnimName = walkAnimName;
    44. }
    45.  
    46. NavComponent.speed = curSpeed;
    47. }
    48.  
    49. functionUpdate(){
    50.  
    51. varcurDistance = Vector3.Distance(target.position, thisObject.position);
    52.  
    53. if (curDistance <= stopDistance){
    54. isChasing = false;
    55. isAttacking = true;
    56. }
    57.  
    58. if (curDistance > stopDistance){
    59. isChasing = true;
    60. isAttacking = false;
    61. }
    62.  
    63. if (isChasing == true){
    64. NavComponent.SetDestination(target.position);
    65. NavComponent.Resume();
    66. thisObject.animation.CrossFade(moveAnimName);
    67. }
    68.  
    69. if (isChasing == false){
    70. NavComponent.Stop();
    71. if (isAttacking == false){
    72. thisObject.animation.CrossFade(idleAnimName);
    73. }
    74. }
    75.  
    76. if (isAttacking == true){
    77. //target.SendMessage( "PlayerDamage"), maximumHitPoints);
    78. //target.GetComponent("PlayerDamageNew").PlayerDamage("5");
    79. thisObject.animation.CrossFade(attackAnimName);
    80. waitFunction();
    81. }
    82. }
    83.  
    84. functionwaitFunction(){
    85. yieldWaitForSeconds (timeToAttack);
    86. //target.GetComponent("PlayerDamageNew").PlayerDamage(5);
    87. }
    88.  
    89. functionPlayerDamage(damage : float){
    90. target.GetComponent("PlayerDamageNew").PlayerDamage(5);
    91.  
    92. }
    93.  
     
  13. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    That means the target of the event (the model itself) doesn't have the function "PlayerDamage" in it's scripts.

    Chances are the zombie script above is on a game object while the animation is on a child model of the object.

    In this case, you'll need a script on the Zombie Model, assuming it's a child of what I'll call the Zombie Object:

    Code (csharp):
    1. zombieModelScript.js
    2.  
    3. function PlayerDamage(damage : float){
    4.      var target : GameObject = transform.parent.GetComponent(zombieObjectScript).target;
    5.      target.GetComponent(PlayerDamageNew).PlayerDamage(damage);   // No quotes around the script name
    6. }
     
  14. gauty40

    gauty40

    Joined:
    Feb 22, 2014
    Posts:
    15
    the animation and script are on the same model but it still had a no reciever error so ive put the script you suggested on it and it now says
    1. "Expressions in statements must only be executed for their side-effects"
    2."Cannot convert "UnityEngine.Transform to UnityEngine.GameObject"
     
  15. infinitypbr

    infinitypbr

    Joined:
    Nov 28, 2012
    Posts:
    3,149
    1. Google it. I'm sure you'll find your answer, which is far more efficient than asking the forum.

    2. That seems pretty obvious. If you need more help, google it. Google everything.