I am working on simple 2D rouge-like game with weapons, currently my weapon script only handles the shooting of weapon once a left mouse button is clicked. What I am looking for is how to make that shooting happen at some interval. So when a user clicks mouse0 the bullet would shoot but there will be a delay before they can shoot again, as well as if the mouse0 is held then the weapon would shoot at specified firing rate.
Code for my controller:
using UnityEngine;
/*
* This class will be attached to every gun to monitor it's actions on key presses.
* WeaponScript will be called and will be attached to each weapon as well depending on it's type with
* stats. WeaponScript will manage how a weapon behaves instead of this script, this is just a mediator
* so I don't have to write this code in abstract class.
*/
public class WeaponPickUpController : MonoBehaviour
{
//public variables
public float pickUpRange; // range at which a gun will be avaialable to pick up, will probably be constant in the end
public GameObject player = null; // stores player object for it's rb component
public Weapon weaponScript = null;
//protected variables
//private variables
private readonly KeyCode _dropKey = KeyCode.Q; // todo: replace to read from config some day
private readonly KeyCode _pickUpKey = KeyCode.E; // todo: replace to read from config some day
private readonly KeyCode _shootKey = KeyCode.Mouse0; // todo: replace to read from config some day
[SerializeField] private bool _equiped = false; // todo: remove serialize field
// Start is called before the first frame update
void Start()
{
// todo: init variables safely here, also perhaps set variable on save file
}
// Update is called once per frame
void Update()
{
var distanceFromPlayer = Vector2.Distance(player.transform.position, this.transform.position);
//Debug.Log(distanceFromPlayer);
// short circuit this if gun is equiped
if (!_equiped && distanceFromPlayer < pickUpRange && Input.GetKeyDown(_pickUpKey)) // todo: a range check here
{
PickUpWeapon();
}
//short circuit this if it is not equiped
if (_equiped && Input.GetKeyDown(_dropKey))
{
DropWeapon();
}
// short circuit if not equiped
if (_equiped && Input.GetKeyDown(_shootKey))
{
weaponScript.Shoot();
}
}
private void PickUpWeapon()
{
_equiped = true;
weaponScript.GetEquiped();
}
private void DropWeapon()
{
_equiped = false;
weaponScript.GetUnequiped();
}
}
Code for Weapon:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Weapon : MonoBehaviour
{
public float damage = 10f;
public float projectileSpeed = 1f;
public float damageBonus = 0f;
[SerializeField] private Rigidbody2D _rb = null;
[SerializeField] private GameObject _player = null;
[SerializeField] private Transform _firePoint = null;
[SerializeField] private GameObject _projectile = null;
//Only called once
private void Start()
{
// load form config
}
// todo: make abstract since this is a superclass
public void Shoot()
{
GameObject projectile = Instantiate(_projectile, _firePoint.position, _firePoint.rotation); // instantiate projectile and make it a child of weapon
Rigidbody2D projectile_rb = projectile.transform.GetComponent<Rigidbody2D>();
//projectile_rb.AddForce(_firePoint.up * projectileSpeed, ForceMode2D.Impulse);
projectile_rb.velocity = _firePoint.up * projectileSpeed;
}
public void GetEquiped()
{
Transform playerTransform = _player.transform;
float playerLocalOffset = _player.GetComponent<BoxCollider2D>().size.x;
_rb.isKinematic = true; // makes this move with the player
transform.rotation = playerTransform.rotation;
transform.SetParent(playerTransform);
transform.localPosition = new Vector3(-playerLocalOffset, 0, playerTransform.position.z);
}
public void GetUnequiped()
{
_rb.isKinematic = false; // makes it dynamic agian
transform.SetParent(null);
transform.position = new Vector3(transform.position.x, transform.position.y, 2);
}
}
Well in your weapon you could have a cooldown like e.g.
public class Weapon : MonoBehaviour
{
...
// How much time has to pass between two bullets fired?
[SerializeField] private float bulletDelay = 0.1f;
// A flag indicating whether this weapon can currently shoot
private bool canShoot = true;
public void Shoot()
{
// while the flag is false do noting, you can't shoot now
if(!canShoot) return;
// ... Your shoot stuff here
// set the flag because you just shot
canShoot = false;
// Invoke the CooldownFinished method after bulletDelay has passed
Invoke(nameof(CooldownFinished),bulletDelay);
}
private void CooldownFinished()
{
canShoot = true;
}
}
If you rather want to provide a "rate" then simply invert
[SerializeField] private float bulletsPerSecond = 10f;
and then
Invoke(nameof(CooldownFinished), 1f / bulletsPerSecond);
Related
I have recently developed in unity and I have a problem with using the raycast.
I created 3 scripts:
Interactor: Hooked to the player, it manages all the raycast features
InteractionObject: Hooked to the objects that need to animate
InteractionRaycast: Hooked to objects that need to be destroyed
Everything works, the only problem is that when the animation of the gameobject takes place (In my case the gameobject was a pillow), the other gameobject (It is under the pillow) is destroyed at the same time as the animation begins.
My goal is to first move the pillow and then click on the gameobject to be destroyed, what can I do?
Thank you in advance for your help
Interactor.cs
public class Interactor : MonoBehaviour
{
[SerializeField]
private float _interctRange;
private InteractionObject _interactObject;
private InteractionRaycast _interactionRaycast;
private Camera _camera;
private RaycastHit _hit;
// Start is called before the first frame update
void Start()
{
_camera = Camera.main;
}
// Update is called once per frame
void Update()
{
if (Input.GetButton("Fire1"))
{
Physics.Raycast(Camera.main.transform.position, Camera.main.transform.forward, out _hit, _interctRange);
if (_hit.transform)
{
_interactObject = _hit.transform.GetComponent<InteractionObject>();
}
if (_interactObject)
{
_interactObject.PerfomAction();
}
}
}
}
InteractionObject.cs
public class InteractionObject : MonoBehaviour
{
[SerializeField]
private Vector3 _openPosition, _closePosition;
[SerializeField]
private float _animationTime;
private Hashtable _iTweenArgs;
[SerializeField]
public bool _isOpen;
// Start is called before the first frame update
void Start()
{
_iTweenArgs = iTween.Hash();
_iTweenArgs.Add("position", _openPosition);
_iTweenArgs.Add("time", _animationTime);
_iTweenArgs.Add("islocal", true);
}
public void PerfomAction()
{
if (Input.GetButton("Fire1"))
{
if (_isOpen)
{
_iTweenArgs["position"] = _closePosition;
}
else
{
_iTweenArgs["position"] = _openPosition;
}
_isOpen = !_isOpen;
iTween.MoveTo(gameObject, _iTweenArgs);
}
}
}
InteractionRaycast.cs
public class InteractionRaycast : MonoBehaviour
{
[SerializeField]
private float _range;
Ray _myRay;
RaycastHit _hit;
// Update is called once per frame
void Update()
{
if (Input.GetButton("Fire1"))
{
Physics.Raycast(Camera.main.transform.position, Camera.main.transform.forward, out _hit, _range);
if (_hit.transform)
{
Destroy(gameObject);
}
}
}
}
Tip: Use RaycastAll() and filter out the objects you want based on conditions.
It might help you with your problem, although you first should pay attention to #derHugo answer. It points out many aspects that you will want to improve in your code.
Your InteractionRaycast will destroy this own gameObject it is attaced to completely regardless of what exactly you are hitting.
You either want to additionally check like e.g.
if (Input.GetButton("Fire1"))
{
var ray = _camera.ViewportPointToRay(new Vector3(0.5f, 0.5f));
if(Physics.Raycast(ray, out var hit, _interctRange))
{
// Is this actually the object that was hit?
if(hit.transform == transform)
{
Destroy(gameObject);
}
}
}
Or - and in general I would do that - instead of having such a component on each and every object you can interact with and shooting hundreds of redundant raycasts, I would rather have a component on your player object, shoot one single raycast and interact with whatever you hit.
Both your target objects can have a common interface
public interface IInteractionObject
{
void PerfomAction();
}
meaning both types need to implement a method called PerformAction without parameters.
And rather interact directly with that in
public class Interactor : MonoBehaviour
{
[SerializeField]
private float _interctRange;
private Camera _camera;
// Start is called before the first frame update
void Start()
{
_camera = Camera.main;
}
// Update is called once per frame
void Update()
{
if (Input.GetButton("Fire1"))
{
var ray = _camera.ViewportPointToRay(new Vector3(0.5f, 0.5f));
// was something hit at all? => Check the API and return values of methods!
if(Physics.Raycast(ray, out var hit, _interctRange))
{
// Did we hit an IInteractionObject
if(hit.transform.TryGetComponent<IInteractionObject>(out var interactable))
{
// This class has no idea what exactly it is interacting with and doesn't need to know
interactable.PerfomAction();
}
}
}
}
}
and then you have different implementations:
public class AnimatedInteractionObject : MonoBehaviour, IInteractionObject
{
[SerializeField] private Vector3 _openPosition;
[SerializeField] private Vector3 _closePosition;
[SerializeField] private float _animationTime;
[SerializeField] public bool _isOpen;
private Hashtable _iTweenArgs;
private void Start()
{
_iTweenArgs = iTween.Hash();
_iTweenArgs.Add("position", _openPosition);
_iTweenArgs.Add("time", _animationTime);
_iTweenArgs.Add("islocal", true);
}
public void PerfomAction()
{
_isOpen = !_isOpen;
// use ternary makes it easier to read
_iTweenArgs["position"] = _isOpen ? _openPosition : _closePosition;
iTween.MoveTo(gameObject, _iTweenArgs);
}
}
and
public class DestroyInteractionObject : MonoBehaviour, IInteractionObject
{
public void PerfomAction()
{
// This is only called by the Interactor
// it already does a key and raycast check so no need to do that here
Destroy(gameObject);
}
}
I'm trying to assign a skill to a projectile that, when used, divides the project into 3 (the original projectile and 2 more new ones).
However, when I instantiate these two clones, I cannot attribute any movement to them. The idea would be for them to take this route:
The green dotted curve indicating the motion of the original bullet, the blue vector indicating the instantaneous velocity of the original bullet at time of special activation, the red vectors indicating the two velocity vectors belonging to each of the newly spawned bullets, and the green angle indicating the direction of the new bullet relative to the original velocity direction
But at the moment, they are just standing at the point of the parabola where they were instantiated. No matter what I do, I can't seem to attach any value to their rigidbody2D. Does anyone know how to fix this?
This is my code so far
Ability Script:
public class AirSpecialSplit : MonoBehaviour, IAirSpecial
{
public float SplitAngleInDegrees = 10;
GameObject bird_down;
GameObject bird_up;
public void ExecuteAirSpecial()
{
{
//hold the velocity of the original bird
Vector2 original_velocity = this.gameObject.GetComponent<Rigidbody2D>().velocity;
//clone two new birds
bird_down = Birb.MakeBirbCopy(this.gameObject);
bird_up = Birb.MakeBirbCopy(this.gameObject);
//get the rigidboy from the clones
Rigidbody2D rb_bird_down = bird_down.GetComponent<Rigidbody2D>();
Rigidbody2D rb_bird_up = bird_up.GetComponent<Rigidbody2D>();
rb_bird_down.velocity = new Vector2(original_velocity.x, original_velocity.y) * Time.deltaTime;
rb_bird_up.AddForce(new Vector2(3, 5) * 500);
}
}
}
Main Bird:
public class Birb : MonoBehaviour
{
#region Provided Code, Do Not Edit
private Rigidbody2D m_rigidbody;
private bool m_canExecuteAirSpecial = true;
public bool CanExecuteAirSpecial
{
get
{
return m_rigidbody.simulated && m_canExecuteAirSpecial;
}
}
private void Awake()
{
m_rigidbody = GetComponent<Rigidbody2D>();
StopBirbSimulation();
}
public void StopBirbSimulation()
{
m_rigidbody.simulated = false;
}
public void StartBirbSimulation()
{
m_rigidbody.simulated = true;
}
public void SetPosition( Vector3 position )
{
if ( m_rigidbody.simulated )
{
m_rigidbody.MovePosition( position );
}
else
{
transform.position = position;
}
}
public void ExecuteAirSpecial()
{
GetComponent<IAirSpecial>().ExecuteAirSpecial();
m_canExecuteAirSpecial = false;
}
private void OnCollisionEnter2D( Collision2D collision )
{
m_canExecuteAirSpecial = false;
}
public static GameObject MakeBirbCopy( GameObject original )
{
Birb newBirb = Instantiate(original).GetComponent<Birb>();
newBirb.m_canExecuteAirSpecial = false;
return newBirb.gameObject;
}
#endregion
[Range( 0, 25 )]
public float LaunchForce = 12;
public void Launch(Vector3 offset, float maximumStretch, Rigidbody2D rigidbody)
{
rigidbody.velocity = new Vector2(offset.x * -LaunchForce, offset.y * -LaunchForce) * (maximumStretch/2);
}
}
Ok, apparently, I just need to set the "simulated" to true after instantiate.
rb_bird_down.simulated = true;
rb_bird_up.simulated = true;
The documentation could be clearer about this since they say that all attributes are copied, which is not the case...
I have some functionalities within some GameObjects.
These functionalities need to be changed when an upgrade is purchased within the game. The problem is that each function is set on its own object.
The first problem is that the variables don't change when I click on the button. As you can see I have added an onclick value to the button, stating that when the button is clicked. The value should change.
The problem here is that "Object reference not set to an instance"
The second problem I face is that each projectile is fired independently. So if I change the static damage of 1 it won't be transferred to other projectiles.
UpgradeMenu
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UpgradeMenu : MonoBehaviour
{
[SerializeField]
private Text accuracyText;
[SerializeField]
private Text speedText;
[SerializeField]
private Text damageText;
[SerializeField]
private float accuracyMultiplier = 0.7f;
private Weapon weapon;
private Projectile projectile;
private Player player;
void OnEnable()
{
UpdateValues();
}
void UpdateValues ()
{
accuracyText.text = weapon.randomAngle.ToString();
damageText.text = projectile.DamageOnHit.ToString();
speedText.text = player.MaxRun.ToString();
}
public void UpgradeAccuracy ()
{
weapon.randomAngle = (int)weapon.randomAngle * accuracyMultiplier;
UpdateValues();
}
public void UpgradeDamage ()
{
projectile.DamageOnHit = (int)projectile.DamageOnHit + 1;
UpdateValues();
}
}
Projectile (DamageScript)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//[RequireComponent (typeof(Rigidbody2D))]
public class Projectile : MonoBehaviour {
[Header ("Speed")]
public float baseSpeed;
public float randomSpeed;
public Vector2 SpeedV2;
public Vector2 Direction;
[Header ("Damage")]
public int DamageOnHit;
[Header ("Layers")]
public LayerMask solid_layer;
public LayerMask entities_layer;
[Header ("OnHit FX")]
public GameObject HitFxPrefab;
public GameObject DustFxPrefab;
[Header ("Bounce")]
public bool BounceOnCollide = false;
public int bouncesLeft = 0;
[HideInInspector]
public Health owner; // owner of the projectile
private Vector2 Position; // Current position
private Vector2 movementCounter = Vector2.zero; // Counter for subpixel movement
public BoxCollider2D myCollider;
List<Health> healthsDamaged = new List<Health>(); // List to store healths damaged
void OnCollideWith (Collider2D col, bool horizontalCol = true) {
var component = col.GetComponent<Health> ();
// If the target the hitbox collided with has a health component and it is not our owner and it is not on the already on the list of healths damaged by the current hitbox
if (component != null && component != owner && !healthsDamaged.Contains(component)) {
// Add the health component to the list of damaged healths
healthsDamaged.Add (component);
// Apply the damage
var didDamage = component.TakeDamage (DamageOnHit);
// Destroy the projectile after applying damage
if (didDamage) {
DestroyMe ();
return;
}
}
// if the projectile hit's a solid object, destroy it
if (col.gameObject.layer == (int)Mathf.Log(solid_layer.value, 2)) {
DestroyMeWall ();
return;
}
}
void OnCollideWithEntity(Collider2D col) {
var component = col.GetComponent<Health> ();
// If the target the hitbox collided with has a health component and it is not our owner and it is not on the already on the list of healths damaged by the current hitbox
if (component != null && component != owner && !healthsDamaged.Contains(component)) {
// Add the health component to the list of damaged healths
healthsDamaged.Add (component);
// Apply the damage
var didDamage = component.TakeDamage (DamageOnHit);
// Destroy the projectile after applying damage
if (didDamage) {
DestroyMe ();
}
}
}
First of all, change
[Header ("Damage")]
public int DamageOnHit;
to static
public static int DamageOnHit = /*your starting value*/;
This ensures that all projectiles will share the same damage it deals on a hit.
For instance, if you currently have 10 projectiles in a scene, and DamageOnHit is 2, they all will deal 2 damage.
Without the static, each of the projectile will have it's own DamageOnHit. This brings us to the next case too:
If each projectile had it's own DamageOnHit, and we want to modify DamageOnHit, we need to specify which projectile's damage to modify.
But if it's static, it becomes much simpler as ALL of the projectile shares the same DamageOnHit.
Now, if you wanted to change the DamageOnHit for ALL projectiles, just do
Projectile.DamageOnHit = /*Your new damage value*/
Also, your null reference exception occured due to the fact that you never did assign your projectile in UpgradeMenu.
(Notice how you never did projectile = /*your projectile*/ in UpgradeMenu.cs?)
By default, that will make the variable null. And trying to do null.DamageOnHit += 1 would make no sense.
Small Edit: Making a variable static would also mean that you can't expose it to the inspector. But you can assign a starting value like the code shown initially.
I created a simple script to move player on Keyboard input, though now I want to move player on touch input, how do I do this ?
Here's my code, so how do I edit this code to make it work ? I have jump working, but dunno how to do it for moving ?
using UnityEngine;
using System.Collections;
public class MoveGround : MonoBehaviour
{
public float y = 0f;
public Rigidbody2D rb;
//public float x = 0f;
//public float z = 0f;
// Use this for initialization
void Start()
{
rb = GetComponent<Rigidbody2D>();
}
// Update is called once per frame
void Update()
{
//move function
if (Input.GetKey(KeyCode.W))
{
rb.velocity = new Vector2(0, y);
}
if (!Input.GetKey(KeyCode.W))
{
rb.velocity = new Vector2(0, 0);
}
if (Input.GetKey(KeyCode.S))
{
rb.velocity = new Vector2(0, -y);
}
//move function end
}
public void Move()
{
}
}
How do you want to move your character using touch input instead of keyboard input? Your current code moves the character depending on whether the "W" or "S" keys are being pushed.
You could make two UI buttons that correspond to "forward" and "backward" keys.
Add an Event Trigger component to those buttons. Add two event types to the buttons: "Pointer Down" and "Pointer Up."
Add a function to the list of each of those events.
Put this code onto an object in your scene.
bool movingForward;
bool movingBackward;
public float speed = 0f;// set this either here in code or in the editor
public Rigidbody2D rb;
void Start (){
rb = GetComponent<Rigidbody2D>();
movingForward = false;
movingBackward = false;
}
// Your forward button will call this function
public void moveForward (){
movingForward = true;
movingBackward = false;
}
// Your backwardbutton will call this function
public void moveBackward (){
movingForward = false;
movingBackward = true;
}
//
public void stopMoving(){
movingForward = false;
movingBackward = false;
}
void Update () {
if(movingForward || movingBackward ){// we're moving
if(movingForward ){// forward
rb.velocity = new Vector2(0, speed);
}else if(movingBackward ){// backward
rb.velocity = new Vector2(0, -speed);
}
}else{// we're moving neither forward nor backward
rb.velocity = new Vector2(0, 0);// so stand still
}
}
Point each function to that object. In the Pointer Down event, choose the "moveForward" function for the button you want to move your character forward, and the "moveBackward" function for the other button.
In the Pointer Up event for both, just choose the "stopMoving" function.
It's really simplistic, but it will work.
I agree with #PolakięGames. The best way to do it is creating two UI buttons with EventTriggers attached and assigning the methods MoveForward and MoveBackwards to PointerDown and StopMoving to PointerDown. Although I would implement it differently:
using UnityEngine;
[RequireComponent(typeof(Rigidbody))]
public class MoveExample : MonoBehaviour {
public float Acceleration = 4f;
public float Speed = 4f;
private Vector2 _velocity = Vector2.zero;
private Rigidbody _rigidbody;
private void Start() {
_rigidbody = GetComponent<Rigidbody>();
}
// movement methods
public void Move(float velocity) { _velocity.x = velocity * Speed; }
public void MoveForward() { Move(1f); }
public void MoveBackwards() { Move(-1f); }
public void StopMoving() { Move(0f); }
private void Update() {
_rigidbody.velocity = Vector2.Lerp(_rigidbody.velocity, _velocity, Time.deltaTime * Acceleration);
}
}
That way your object will move smootly and you can control the speed and acceleration. You have to change Rigidbody to Rigidbody2D if you intend to use it with 2D Physics.
My project is a military fps and i'm having some problems with animations.
I have 3 different weapons, 1 animator controller for each one and every weapon has a "enter" and "leave" animation. Like CS, COD, etc...
I need to know when my "leave" animation ends to disable the gameobject, enable the other one and play the "enter" animation.
I tryed to do this: http://answers.unity3d.com/questions/362629/how-can-i-check-if-an-animation-is-being-played-or.html but without sucess.
I'll leave here a print of the animator controller, the hierarchy and the script, if u need more details, just need to say.
Animator controller of the weapon number 1
All transitions to "Sair" (leave animation) have a trigger (AK47_sair) and the transition to "Extit" state have a trigger ("AK47_SairControlador")
On my code, when i press 2 (change to weapon number 2) i want to do the transition.
This is the hierarchy, my script is attached to "Jogador".
With my actual code, it disable tha AK47 gameobject when the leave animation still playing.
using UnityEngine;
using System.Collections;
public class FirstPerson : MonoBehaviour {
public float speed;
public float normalSpeed = 5.0f;
public float slowSpeed = 2.5f;
public float crchSpeed = 2.5f;
private Transform tr;
private float dist; // distance to ground
public float mouseSensitivity = 5.0f;
public float verticalRotation = 0.0f;
public float updownRange = 60.0f;
private float verticalSpeed = 0.0f;
public float jumpSpeed = 5.0f;
CharacterController player;
private GameObject AK47;
private GameObject Faca;
public float shootingRate = 0.15f;
public float shootCooldown;
private bool agachado = false;
public float camOriginalPositionY;
public float camCrouchPositionY;
private Animator controladorAnimacaoAK;
private Animator controladorAnimacaoFaca;
public CapsuleCollider playerCollider;
public Camera CameraPrincipal;
public int ArmaSelecionada;
public int UltimaArma;
void Start () {
player = GetComponent<CharacterController>();
shootCooldown = 0;
controladorAnimacaoAK = player.GetComponentInChildren<Animator>();
playerCollider = gameObject.GetComponent<CapsuleCollider> ();
CameraPrincipal = Camera.main;
ArmaSelecionada = 1;
AK47 = CameraPrincipal.transform.FindChild ("ak47_final_animado").gameObject;
}
void Update () {
if (Input.GetKeyDown (KeyCode.Alpha2) || Input.GetKeyDown(KeyCode.Keypad2)) {
UltimaArma = ArmaSelecionada;
ArmaSelecionada = 2;
if(UltimaArma == 1) {
controladorAnimacaoAK.SetTrigger("AK47_Sair");
controladorAnimacaoAK.SetTrigger("AK47_SairControlador");
AK47.SetActive (false);
}
}
if (Input.GetKeyDown (KeyCode.Alpha1)) {
UltimaArma = ArmaSelecionada;
ArmaSelecionada = 1;
// controladorAnimacaoAK.SetTrigger ("AK47_Entrar");
}
if (ArmaSelecionada == 1) {
// diz ao controlador da anim se o player esta a movimentar-se ou nao
controladorAnimacaoAK.SetFloat ("AK47_Deslocacao", player.velocity.magnitude);
//Debug.Log (player.velocity.magnitude);
// dispatar tiros
PlayerShoot PlayerShootScript = player.GetComponent<PlayerShoot> ();
if (shootCooldown > 0) {
shootCooldown -= Time.deltaTime;
}
if (Input.GetButton ("Fire1")) {
if (shootCooldown <= 0) {
shootCooldown = shootingRate;
PlayerShootScript.FireShoot ();
// animaçao
controladorAnimacaoAK.SetBool ("AK47_Disparar", true);
}
} else {
// animaçao
controladorAnimacaoAK.SetBool ("AK47_Disparar", false);
}
if (Input.GetKeyDown (KeyCode.R)) {
controladorAnimacaoAK.SetTrigger ("AK47_rec");
}
}
}
}
In Unity 5 you've got this new thing which is Animation State Machine Behavior : State Machine Behaviours
You can use it to specify behavior when the Animation Controller enters or leave specific states.
For exemple here I've got my door which have an Open and Close state, and let's say that I want to play a sound when the door is opening.
Here I clicked Opening, then Add Behaviour and set a random name for the test (Behavior Test in my case)
Then I just need to implement the function void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) to play a sound at the first frame the animation is running.
[SerializeField]
AudioClip open_sound;
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
animator.GetComponent<AudioSource>().clip = open_sound;
animator.GetComponent<AudioSource>().Play();
}
In your case, you would want to implement a behavior in the state Disparar which implements the function OnStateExit([...]) and handle the weapon change.
To go a bit further I don't think you should handle the weapon change directly in the animation state, but maybe your script could send an event catched by a Game Controller that will actually handle the change of weapon.