Control animation and execute script when animation ends - c#

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.

Related

Handling automatic weapons in Unity 2D

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);

I want to make my unity gun script for vr be automatic, any way to do it with this script?

I want it so that if my player holds down the fire button, the gun will be automatic firing so that if I hold the trigger the gun will keep shooting, any way to do that (with fire rate) with this script?
On my image the top is my XR grab interactable.
The button I use to fire is my trigger.
XR grab interactable on top, gun script on bottom
using System.Collections.Generic;
using UnityEngine;
public class Gun : MonoBehaviour
{
public float speed = 40;
public GameObject bullet;
public Transform barrel;
public AudioSource audioSource;
public AudioClip audioClip;
public void Fire()
{
GameObject spawnedBullet = Instantiate(bullet, barrel.position, barrel.rotation);
spawnedBullet.GetComponent<Rigidbody>().velocity = speed * barrel.forward;
audioSource.PlayOneShot(audioClip);
Destroy(spawnedBullet, 2);
}
}
In your case you already have to events: OnActivate and OnDeactivate so you could modify your code like this
public class Gun : MonoBehaviour
{
public float speed = 40;
public GameObject bullet;
public Transform barrel;
public AudioSource audioSource;
public AudioClip audioClip;
// configure shots per second
public float rate = 1;
private Coroutine _current;
public void BeginFire()
{
if(_current != null) StopCoroutine(_current);
_current = StartCoroutine (FireRoutine ());
}
public void StopFire()
{
if(_current != null) StopCoroutine(_current);
}
private IEnumerator FireRoutine()
{
while(true)
{
GameObject spawnedBullet = Instantiate(bullet, barrel.position, barrel.rotation);
spawnedBullet.GetComponent<Rigidbody>().velocity = speed * barrel.forward;
audioSource.PlayOneShot(audioClip);
Destroy(spawnedBullet, 2);
yield return new WaitForSeconds (1f / rate);
}
}
}
And then as so far in OnActivate call the BeginFire and in the OnDeactivate call the StopFire

Method ignores class scope variable value change

The issue is with the boolean isDodging. It is set to true in the Dodge() method. That should trigger an if statement in the Movement() method (called in FixedUpdate()) but that block is always skipped over. I'm attaching all of the class' code, because there must be something I'm missing here:
using UnityEngine;
public class MovementController
{
/* COMPONENTS */
public Rigidbody2D Rigidbody { private get; set; }
/* VARIABLES */
private bool isDodging = false;
private Vector2 dodgeDirection = Vector2.right;
private float dodgeDuration = 1f;
private float dodgeSpeed = 20f;
private float timer = 0f;
/* METHODS */
// Called in fixed update (since it's dealing with physics)
public void Movement(Vector2 currentPosition, Vector2 velocity)
{
Debug.Log("In movement: " + isDodging);
if (isDodging)
{
Debug.Log("Dodge 3");
Move(currentPosition, dodgeDirection * dodgeSpeed);
timer += Time.fixedDeltaTime;
if (timer >= dodgeDuration)
{
Debug.Log("Stopped dodging " + Time.fixedTime);
isDodging = false;
}
}
else
{
Move(currentPosition, velocity);
}
}
private void Move(Vector2 currentPosition, Vector2 velocity)
{
if (Rigidbody == null)
{
Debug.LogWarning("No rigidbody to move!");
return;
}
Rigidbody.MovePosition(currentPosition + (velocity * Time.fixedDeltaTime));
}
// Must be called while Movement is being called
public void Dodge(Vector2 direction, float maxSpeed, float speedMultiplier = 2f, float duration = 1f)
{
if (direction == Vector2.zero) { return; }
Debug.Log("Dodge 1 " + isDodging);
dodgeDirection = direction;
dodgeDuration = duration;
dodgeSpeed = maxSpeed * speedMultiplier;
isDodging = true;
Debug.Log("Dodge 2" + isDodging + Time.fixedTime);
timer = 0f;
}
}
The thing is, the "In movement: " log always shows isDodging as false, and the if block under it never runs. Meanwhile, "Dodge 2" will show true (as isDodging is changed right above it). And the weirdest: "Dodge 1" shows false the first time Dodge() is called, but true everytime its called after that - as if isDodging was changed to true in the class scope, and Movement() doesn't recognize that for some reason.
Both this functions are called in a separate MonoBehaviour:
public class CreatureMovement : MonoBehaviour
{
[Header("Movement")]
[SerializeField] protected Vector2Reference moveDirection;
[SerializeField] protected FloatReference maxSpeed;
[Header("Dodge")]
[SerializeField] private FloatReference dodgeDuration;
[SerializeField] private FloatReference dodgeSpeedMultiplier;
[Header("References")]
[SerializeField] private new Rigidbody2D rigidbody;
private readonly MovementController movement = new MovementController();
public float MaxSpeed { get => maxSpeed; }
private float speed;
private float Speed { get => speed; set => speed = Mathf.Clamp(value, 0, maxSpeed); }
public virtual Vector2 Velocity
{
get => moveDirection.Value * Speed;
set
{
moveDirection.SetValue(value.normalized);
Speed = value.magnitude;
}
}
private void Start() => movement.Rigidbody = rigidbody;
private void FixedUpdate() => movement.Movement(transform.position, Velocity);
public void Dodge() => movement.Dodge(moveDirection, maxSpeed, dodgeSpeedMultiplier, dodgeDuration);
}
Where Dodge() is called from player input.
Except for dodging, movement is ocurring exactly as expected. The problem probably isn't in the Move() method, as it doesn't have isDodging in it.
I have absolutey no idea why this is happening, the code seems so simple to me, but it just isn't working. Please help out with this.
You're calling Dodge on a prefab instead of the scene instance of that prefab which is running CreatureMovement.FixedUpdate.
I believe you can verify this by placing this code in Dodge (Source: Max-Pixel):
if (gameObject.scene.name == null) Debug.Log("It's a prefab!");
You need to change your input processing to call Dodge on the instance in the scene instead of a prefab.
You can do that by dragging the instance in the scene into the button onclick event, then selecting Dodge. Or, if you are spawning the object dynamically, you could, in Start, find a reference to the button, and add Dodge to its onClick listeners:
private void Start()
{
movement.Rigidbody = rigidbody;
// something along the lines of this...
Button buttonRef = GameObject.Find("ButtonName").GetComponent<Button>();
buttonRef.onClick.AddListener(Dodge);
}

Changing data on click button

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.

Trying to launch a projectile towards a gameobject, doesn't move!=

I'm making a 2D Tower Defense game and want my towers to launch a prefab at minions. However it currently only spawns my desired prefab, but doesn't move it.
My two scripts:
public class Attacker : MonoBehaviour {
// Public variables
public GameObject ammoPrefab;
public float reloadTime;
public float projectileSpeed;
// Private variables
private Transform target;
// Use this for initialization
void Start () {
}
// Update is called once per frame
void Update () {
}
void OnTriggerEnter(Collider co){
if (co.gameObject.tag == "Enemy" || co.gameObject.tag == "BlockTower") {
Debug.Log("Enemy tag detected");
if(this.gameObject.tag == "Enemy" && co.gameObject.tag != "Enemy"){
Debug.Log("This is an Enemy");
// Insert for Enemey to attack Block Towers.
}
if(this.gameObject.tag == "Tower" && co.gameObject.tag != "BlockTower"){
Debug.Log("This is a Tower");
Tower Tower = GetComponent<Tower>();
Tower.CalculateCombatTime(reloadTime, projectileSpeed);
Transform SendThis = co.transform;
Tower.SetTarget(SendThis);
}
}
}
}
and
public class Tower : MonoBehaviour {
private Transform target;
private float fireSpeed;
private double nextFireTime;
private GameObject bullet;
private Attacker source;
// Use this for initialization
public virtual void Start () {
source = this.GetComponent<Attacker> ();
}
// Update is called once per frame
public virtual void Update () {
if (target) {
Debug.Log("I have a target");
//if(nextFireTime <= Time.deltaTime)
FireProjectile ();
}
}
public void CalculateCombatTime(float time, float speed){
Debug.Log("Calculate Combat Speed");
nextFireTime = Time.time + (time * .5);
fireSpeed = speed;
}
public void SetTarget(Transform position){
Debug.Log("Set Target");
target = position;
}
public void FireProjectile(){
Debug.Log("Shoot Projectile");
bullet = (GameObject)Instantiate (source.ammoPrefab, transform.position, source.ammoPrefab.transform.rotation);
float speed = fireSpeed * Time.deltaTime;
bullet.transform.position = Vector3.MoveTowards (bullet.transform.position, target.position, speed);
}
}
Basicly Attacker detects the object that collides with it, then if its tag is Tower it will send the information to Tower. My debug shows that every function works, even "Debug.Log("Shoot Projectile");" shows up.
However it doesn't move towards my target so I guess "bullet.transform.position = Vector3.MoveTowards (bullet.transform.position, target.position, step);" is never being executed?
Vector3.MoveTowards only moves the object once, it's just a instant displacement when the FireProjectile is called.
You need to create some kind of projectile script with an Update() function to make it move over time.
Here is an example:
public class Projectile : MonoBehaviour
{
public Vector3 TargetPosition;
void Update()
{
transform.position = Vector3.MoveTowards(transform.position, TargetPosition, speed * Time.DeltaTime);
}
}
Then right after your bullet instantiation, set the target:
bullet.GetComponent<Projectile>().TargetPosition = target.position;
Hope it helps.
You have to update the position of the bullet. You are only moving when you create the bullet.
Try to make a list of bullets and use the update function to change the position.

Categories