Search Unity

How do I best handle attacks on different enemy types?

Discussion in 'Scripting' started by FarzanZand, Mar 23, 2014.

  1. FarzanZand

    FarzanZand

    Joined:
    Jul 29, 2013
    Posts:
    14
    Hi there!
    I am working on a 2D-platformer similar to Castlevania, and I am having trouble figuring out how to handle attacks on different types of enemies.

    I have tried have one complete script with each attack on in for every type, but that leads to several script that are all the same with a few differences, and if I want to change the attack on one, i need to update them all.

    What I hope for is the following.

    - playerController controls the animations and movements
    - AttackController holds all my attack codes
    - "EnemyType"Controller that has the enemy functions, including what happens when they get attacked. One script per enemy type.

    For instance, when I hit a beholder, the "Damaged(); function is called in the beholder script. When I hit a skeleton, the "Damaged();" is called in the skeleton script instead.

    I have the PlayerController script and AttackController up and running. An animation with a trigger collider for contact with weapon. But I have no idea how to continue on from there. How do I best handle that one attack script has different effects on different enemy types? I want my attackscript to call a method on the enemy script, but since the enemies are different, i can't just attach a GetComponent because I don't know how to make it dynamic, and not just work with one type specified by code.

    How do you handle attacks from one player towards different enemy types? I am open for any suggestions! Been stuck all day.

    I attached my script below, for those interested.

    Thanks for taking the time to read my post mates. I hope you can help me with my troubles!

    PlayerController

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class PlayerController : MonoBehaviour {
    5.    
    6.     public Animator anim;
    7.     public SpriteRenderer renderer;
    8.  
    9.     public float speed = 2.0f;
    10.     public float jumpForce = 250.0f;
    11.     public Transform groundCheck;
    12.     public bool grounded = true;
    13.     private bool jump;
    14.     private int jumpCounter = 0;
    15.     public bool attack = false;
    16.     public bool invul = false;
    17.     private bool fallInvul = false;
    18.     public int health = 1000;
    19.  
    20.  
    21.  
    22.     void Start () {
    23.  
    24.  
    25.         //Gets components
    26.         anim = gameObject.GetComponent<Animator>();
    27.         renderer = GetComponent<SpriteRenderer>();
    28.  
    29.         //Sets the groundcheckobject
    30.         GameObject groundCheckObject = GameObject.Find ("GroundCheck");
    31.         groundCheck = groundCheckObject.transform;
    32.        
    33.     }
    34.    
    35.    
    36.     void Update () {
    37.        
    38.         if(health <= 0){
    39.             Destroy(gameObject);
    40.             Debug.Log ("DESTROYED");
    41.         }
    42.  
    43.  
    44. //*******GROUNDED**************
    45.         // The player is grounded if a linecast to the groundcheck position hits anything on the ground layer.
    46.         if(Physics2D.Linecast(transform.position, groundCheck.transform.position, 1 << LayerMask.NameToLayer("Ground"))){
    47.             grounded = true;  
    48.             anim.SetBool("Grounded", true);
    49.         }
    50.         else{
    51.             grounded = false;
    52.             anim.SetBool("Grounded", false);
    53.  
    54.         }
    55.        
    56.        
    57.        
    58. //*****HORIZONTAL MOVEMENT*******
    59.        
    60.         //Mathf.abs is used to make sure that all negatives becomes positives.
    61.         anim.SetFloat("Speed", Mathf.Abs (Input.GetAxis ("Horizontal")));
    62.  
    63.         if(Input.GetAxis ("Horizontal") < 0  !attack)
    64.         {
    65.             Vector2 newScale = transform.localScale;
    66.             newScale.x = -1.0f;
    67.             transform.localScale = newScale;
    68.         }
    69.        
    70.         else if (Input.GetAxis("Horizontal") > 0  !attack)
    71.         {
    72.             Vector2 newScale = transform.localScale;
    73.             newScale.x = 1.0f;
    74.             transform.localScale = newScale;
    75.         }
    76.         if(!attack  !invul)
    77.         transform.position += transform.right *Input.GetAxis ("Horizontal")* speed * Time.deltaTime;
    78.  
    79.  
    80. //************JUMP**********
    81.  
    82.         if (Input.GetButtonDown("Jump")  grounded)
    83.         {
    84.             jump = true;
    85.             jumpCounter = 0;
    86.         }
    87.  
    88.         //Second Jump
    89.         if(jumpCounter == 1  Input.GetButtonDown ("Jump"))
    90.         {
    91.             jumpCounter = 0;
    92.             rigidbody2D.velocity = new Vector2(0,0); //Resets velocity
    93.             anim.SetTrigger("DoubleJump");
    94.             rigidbody2D.AddForce(new Vector2(0f, jumpForce));
    95.            
    96.             Debug.Log ("DoubleJump");
    97.         }
    98.         //First Jump
    99.         if(jump  jumpCounter == 0)
    100.             {
    101.                 anim.SetTrigger("Jump");
    102.                 rigidbody2D.AddForce(new Vector2(0f, jumpForce));
    103.                 jumpCounter = 1;
    104.                 jump = false;
    105.                 Debug.Log ("FirstJump");
    106.             }
    107.  
    108.         //Invul until groudned when damaged
    109.         if(fallInvul)
    110.             FallInvul();
    111.  
    112.     }
    113.    
    114.     //************DAMAGED*************
    115.     public void StartDamageAnim(){
    116.         if (invul == false){
    117.         StartCoroutine(DamagedAnim());
    118.         StartCoroutine(FlashRed(0.3f));
    119.         }
    120.         else
    121.             return;
    122.     }
    123.  
    124.         //Makes you invul/stunned, shows anim.
    125.     public IEnumerator DamagedAnim(){
    126.         jumpCounter = 0;
    127.         invul = true;
    128.         anim.SetTrigger("Damage");
    129.         health -= 100;
    130.         rigidbody2D.velocity = new Vector2(0,0); //Resets velocity
    131.  
    132.         if(transform.localScale.x <= -0.1f)
    133.         rigidbody2D.AddForce(new Vector2(50f, 50f));
    134.         else
    135.             rigidbody2D.AddForce(new Vector2(-50f, 50f));
    136.  
    137.  
    138.         //Stun duration depending on grounded or not
    139.     if(grounded){
    140.             yield return new WaitForSeconds(0.4f);
    141.             invul = false;
    142.         }
    143.         else{
    144.             //makes you invulnerable until grounded again, for long falls
    145.             yield return new WaitForSeconds(0.4f);
    146.             fallInvul = true;
    147.         }
    148.            
    149.  
    150.          
    151.     }
    152.  
    153.     //Flash color red when attacked
    154.     public IEnumerator FlashRed(float time){
    155.     renderer.color = new Color(1f,0.2f,0.2f,1f);
    156.         yield return new WaitForSeconds(time);
    157.         renderer.color = new Color(1f,1f,1f,1f);
    158.     }
    159.  
    160.     private void FallInvul(){
    161.         if(grounded){
    162.             fallInvul = false;
    163.             invul = false;
    164.         }
    165.     }
    166.  
    167. }
    168.  
    PlayerAttacks

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class PlayerAttacks : MonoBehaviour {
    5.  
    6.     public Animator anim;
    7.     private PlayerController playerController;
    8.     public int weaponDamage;
    9.  
    10.     // Use this for initialization
    11.     void Start () {
    12.  
    13.         //Sets starting weapon damage
    14.         WeaponDamage();
    15.  
    16.         //Gets components
    17.         anim = gameObject.GetComponent<Animator>();
    18.    
    19.         //Create object reference for PlayerController script
    20.         playerController = gameObject.GetComponent<PlayerController>();
    21.     }
    22.    
    23.     // Update is called once per frame
    24.     void Update () {
    25.    
    26.     //****************IF GROUNDED ACTIONS****************
    27.  
    28.         if(playerController.grounded == true){
    29.             //Basic attack
    30.             if(Input.GetKeyDown(KeyCode.F)  !playerController.attack){
    31.                 StartCoroutine(Attack1());
    32.         }
    33.     }
    34.  
    35.     }
    36.  
    37.     //************ ATTACK FUNCTIONS ******************
    38.  
    39.     public void WeaponDamage(){
    40.         //Update this script later for weapon swaps and stat changes
    41.         weaponDamage = 100;
    42.     }
    43.  
    44.     //Basic attack1
    45.     IEnumerator Attack1(){
    46.         anim.SetTrigger("Attack1");
    47.         playerController.attack = true;
    48.         yield return new WaitForSeconds(0.3f);
    49.         playerController.attack = false;
    50.         }
    51.  
    52.     IEnumerator AirAttack(){
    53.         //Fix this after attack1 works
    54.         return null;
    55.     }
    56.  
    57. }
    58.  
    Example Enemy Script: Beholder
    Code (csharp):
    1.  
    2.  
    3. using UnityEngine;
    4. using System.Collections;
    5.  
    6. public class Beholder : MonoBehaviour {
    7.  
    8.     public SpriteRenderer renderer;
    9.     public bool invul = false;
    10.     public int health = 1000;
    11.     public Animator anim;
    12.  
    13.     private PlayerController playerController;
    14.     private PlayerAttacks playerAttacks;
    15.  
    16.     // Use this for initialization
    17.     void Start () {
    18.         GameObject player = GameObject.Find ("Player");
    19.         playerController = player.GetComponent<PlayerController>();
    20.         playerAttacks = player.GetComponent<PlayerAttacks>();
    21.         renderer = GetComponent<SpriteRenderer>();
    22.     }
    23.  
    24.     //Damages player when touched
    25.     void OnTriggerEnter2D(Collider2D other)
    26.     {
    27.         if (other.tag == "Player"  playerController.invul == false){
    28.             playerController.StartDamageAnim();
    29.         }
    30.     }
    31.  
    32.     void DamagedStart(int damage){
    33.         if(!invul){
    34.         StartCoroutine(Damaged());
    35.         FlashRed (0.3f);
    36. }
    37.     }
    38.    
    39.     //Makes beholder invul/stunned to avoid to much damage, shows anim.
    40.     public IEnumerator Damaged(){
    41.         invul = true;
    42.         health -= playerAttacks.weaponDamage; //Damage from attack-script
    43.  
    44.         //Knockback
    45.         rigidbody2D.velocity = new Vector2(0,0);
    46.         if(transform.localScale.x <= -0.1f)
    47.             rigidbody2D.AddForce(new Vector2(50f, 50f));
    48.         else
    49.             rigidbody2D.AddForce(new Vector2(-50f, 50f));
    50.    
    51.  
    52.             yield return new WaitForSeconds(0.4f);
    53.         invul = false;
    54.     }
    55.     //Flashes sprite red when damaged
    56.     public IEnumerator FlashRed(float time){
    57.         renderer.color = new Color(1f,0.2f,0.2f,1f);
    58.         yield return new WaitForSeconds(time);
    59.         renderer.color = new Color(1f,1f,1f,1f);
    60.     }
    61.  
    62. }
     
    Last edited: Mar 23, 2014
  2. Fellshadow

    Fellshadow

    Joined:
    Dec 2, 2012
    Posts:
    169
    I'm not very experienced with Unity, but I had this exact same issue. I wanted to call the same function from different scripts.
    The way I ended up doing this was by using SendMessage. As long as the object you are sending the message to has a function with the specified name, it will be called.
    SendMessage can only have one parameter, however. You can make some sort of holder class that has multiple values in it, and send that to be able to send multiple values, though.

    Be aware that SendMessage does have a larger overhead than GetComponent (from what I've read, at least), so you only want to use it when you have to.
     
  3. renman3000

    renman3000

    Joined:
    Nov 7, 2011
    Posts:
    6,697
    I am not sure if this will help, but I created a mega man style game that required the enemy to know what weapon the player was hitting him with. This "knowledge" would give the correct damage. For instance enemy x gets 10 points damage from the c gun. But c gun does 30 points damage on the y enemy.


    Each enemy class has its own script, custom for it. Each enemy also has a damage taker script. Each enemy, custom script holds an array marking the damage levels of each gun the player could use. Each enemy custom script then passes this array to its damage taker script.


    This results in a system where each enemy class has defined in code the damage levels caused by which ever weapon the character happens to be using at any given time.
     
    Last edited: Mar 24, 2014
  4. rrh

    rrh

    Joined:
    Jul 12, 2012
    Posts:
    331
    I would either use SendMessage like Fellshadow said, or I would use inheritance and override virtual functions.

    This explains inheritance in more detail
    http://unity3d.com/learn/tutorials/modules/intermediate/scripting/overriding

    So you could make a base Enemy class that has a virtual Damaged function and then Skeleton and Beholder classes extend the Enemy class and override the Damaged function with their own behaviour.
     
  5. Fellshadow

    Fellshadow

    Joined:
    Dec 2, 2012
    Posts:
    169
    Oh yeah, I never even thought about inheritance.
    Can you call GetComponent<BaseClass> even if only a child class is attached though?

    Otherwise you'd still have to use SendMessage, wouldn't you?
     
  6. renman3000

    renman3000

    Joined:
    Nov 7, 2011
    Posts:
    6,697
    Yes,
    In my method, I did use SendMessage as well. Generally speaking the only other alternative is GetComponent. I measured that method to be too costly given the random and frequent behavior of the scenario.
     
  7. FarzanZand

    FarzanZand

    Joined:
    Jul 29, 2013
    Posts:
    14
    Thank you very much! SendMessage seems to be exactly what I need, and once I get that up and running, I will take a look at making it work together with inheritance as well.

    Really mates, I appreciate it. You made my work move forward from its frozen state. :)
     
  8. Zaladur

    Zaladur

    Joined:
    Oct 20, 2012
    Posts:
    392
    Yes, GetComponent<BaseClass> gets all types that inherit from the Base class. This sort of scenario is exactly what inheritance is meant to be used for - I prefer it over the SendMessage solution in a case like this.