Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Weapon System and Animation Events

Discussion in 'Scripting' started by erebel55, May 22, 2015.

  1. erebel55

    erebel55

    Joined:
    Jun 14, 2014
    Posts:
    43
    I have started setting up a weapon system for a multiplayer fps.

    I am trying to use single responsibility and stay away from manager classes.

    So, I have created the following interfaces

    Code (CSharp):
    1.  public interface ISwingable
    2. {
    3.      void Swing();
    4. }
    Code (CSharp):
    1.  public interface IShootable
    2. {
    3.      void Shoot();
    4. }
    The weapons will drive the player animation. For example, an Axe or Sword would drive a swing animation. And a AK47 or pistol would drive a shoot animation.

    I created an AttackAnimation class that is attached to the player and drives their animation.

    Code (CSharp):
    1.  // TODO: do we want this on the player or weapon??
    2. public class AttackAnimation : MonoBehaviour
    3. {
    4.      Animator anim;
    5.      bool isMounted = false;
    6.      void Awake()
    7.      {
    8.          anim = GetComponent<Animator>();
    9.      }
    10.      void Start()
    11.      {
    12.          // activate the attack animation layer
    13.          anim.SetLayerWeight(1, 1f);
    14.      }
    15.      void Update()
    16.      {
    17.          anim.SetInteger("CurrentAction", 0);
    18.          if (Input.GetButton("Fire1"))
    19.          {
    20.              anim.SetInteger("CurrentAction", 1);
    21.          }
    22.      }
    23. }
    I then implemented my ISwingable and IShootable interfaces.

    Code (CSharp):
    1.  public class MeleeAttack : MonoBehaviour, ISwingable
    2. {
    3.      public Transform collisionPoint;
    4.      public void Swing()
    5.      {
    6.          Collider[] hits = Physics.OverlapSphere(collisionPoint.position, .5f);
    7.          foreach (Collider hit in hits)
    8.          {
    9.              if (hit.transform.root != transform) // can use this or layers to stop from hitting yourself
    10.              {
    11.                  //Debug.Log(hit.name);
    12.                  ActivateHittables.HitAll(hit.gameObject);
    13.              }
    14.          }
    15.      }
    16. }
    Code (CSharp):
    1. public class RangedAttack : MonoBehaviour, IShootable
    2. {
    3.      public float range = 500f;
    4.      void Update()
    5.      {
    6.          if (Input.GetButton("Fire1"))
    7.          {
    8.              Shoot();
    9.          }
    10.      }
    11.      public void Shoot()
    12.      {
    13.          RaycastHit hit;
    14.          if (Physics.Raycast(Camera.main.transform.position, Camera.main.transform.forward, out hit, range))
    15.          {
    16.              ActivateHittables.HitAll(hit.collider.gameObject);
    17.          }
    18.      }
    19. }
    I was going to have MeleeAttack::Swing() called by an Animation Event on the player's swing animation.

    What is the best way to organize these scripts?

    Would it be better to have MeleeAttack and RangedAttack attached to the Player or the Weapon's game object?

    If they are attached to the weapon I'm not sure how to call MeleeAttack::Swing() via an animation event.

    Could anyone help me organize this a bit? I'm trying to setup a weapon system that conforms to single responsibility.

    Thank you
     
  2. MysterySoftware

    MysterySoftware

    Joined:
    Sep 18, 2014
    Posts:
    46
    I like your idea but I would probably recommend setting up a weapon manager script attached to your player and a custom script for every kind of melee or ranged weapon. Your manager could then check based on the weapon's type which animation to use and let the weapon script do the actual attack stuff :)
     
  3. erebel55

    erebel55

    Joined:
    Jun 14, 2014
    Posts:
    43
    I am actually purposely trying to avoid Manager classes, as *in most cases* I see them as bad code structure.
     
    Last edited: May 27, 2015
  4. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,513
    What are you defining a manager class as?




    How I'd go about this is I'd scrap the 'AttackAnimation' component all together. That's not the animation, the Animation is inside the Animator. You just need to tell the Animator to play the animation.

    What would do that? Personally I have what I call CombatMotors. This is a script that handles combat logic. This would check for user input and decide if swinging is necessary. It should tell the Animator to play some animation on some event.

    I also notice your AttackAnimation is setting some condition to 0 every frame, and 1 for a single frame when the input is registered as being head... usually a 'swing' animation or a 'fire' animation isn't something that only plays when the button is down. Usually it's something that is 'triggered' when the button is initially pressed. Use Input.GetButtonDown to know when the thing was pressed down. Animator also has a trigger parameter in its configuration. This sends a quick signal to change the state of the mecanim state machine. If there are multiple states to select from you could use the int to flag which one to select ON trigger.

    Now, this CombatMotor can receive the event back when the animation reaches that event (I personally hate the animation events in unity... there's some bad design). The CombatMotor can than have a reference to the weapon it's swinging around. And it can call the 'Swing' method on that weapon so it does it's dealy.
     
  5. erebel55

    erebel55

    Joined:
    Jun 14, 2014
    Posts:
    43
    My AttackAnimation is what I'm using to "tell the Animator to play the animation".

    When the fire button is clicked I change the current action to an integer which then starts the appropriate animation. This integer is a literal right now, but will eventually be a public property on the weapon game object.

    I am setting the CurrentAction parameter back to 0 so that the attack animation isn't repeated (unless the fire button is clicked again).
    The animation still finishes because it's transition back is setup using exit time. So, essentially this is working as a trigger would.
    Code (CSharp):
    1. anim.SetInteger("CurrentAction", 0);
    My question was more around how can I call an animation event function that isn't on a script attached to the same game object as the Animator.

    The only way I can think of is to have the animation event be a middle man function created on the same game object as the Animator. Whose only purpose is to call the appropriate function that the animation event cannot see.
     
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,513
    This is exactly what the 'trigger' parameter in mecanim is for. It does it with out you have to constantly update every frame.

    You can't, you'll need some receiver on the same gameobject as the Animator.

    You can use this 'AttackAnimation' component to be that receiver.

    I only suggested NOT calling it an AttackAnimation as it technically isn't the animation... it's the script that decides when an animation will play based on combat logic. I would call that a combat script... or CombatMotor.

    Yep, just like I suggested.

    This limitation of the mecanim event system is one reason I don't like it. It assumes all scripts will be on a single GameObject.

    UGH, if I put ALL my scripts for my player on a single GameObject it'd be INTENSE to look at.
     
    erebel55 likes this.
  7. erebel55

    erebel55

    Joined:
    Jun 14, 2014
    Posts:
    43
    Right, I will have to look into triggers. Would I need two animation parameters if I have multiple states? Or would the int not have to be an animation parameter?

    And I was using GetDown instead of GetButtonDown because I am using PhotonAnimatorView and I noticed that it seems to miss the parameter update over the network if I only send it on 1 frame.

    And yes my class naming has never been the best. I will make that change :)
     
    Last edited: May 27, 2015