Hi,
I have 3 animation A,B,C.
I want to play animation A then B then C. When animation C is completed, I will show a notice.
How can I do that? I don’t know how to callback when animation complete .
Thanks so much.
Hi,
I have 3 animation A,B,C.
I want to play animation A then B then C. When animation C is completed, I will show a notice.
How can I do that? I don’t know how to callback when animation complete .
Thanks so much.
Use CrossFadeQueued() so the animations will play one after the other. To check if an animation has finished playing, store the AnimationState in a temporary variable:
Note: I am using C#
Add this as a member variable to your class:
AnimationState anim;
Put this in Start() (change nameOfAnimation to the actual animation name):
anim = animation["nameOfAnimation"];
Then put this in Update():
if (anim.normalizedTime == 1.0f)
{
// animation is finished. do something here
}
I have not tested this code so tell me if there’s anything wrong.
It is not work. Because normalize time is checked before animation complete.
You can use animation.Crossfade( ) function.
void Update()
{
animation.CrossFade("attack");
if (anim.normalizedTime == 1.0f)
{
// do something..
}
}
The code in do something is not work
Please help me
Again, I said use CrossFadeQueued, not CrossFade, so that they will be played one after the other.
AnimationState anim;
void Start()
{
animation.CrossFadeQueued("animation1");
animation.CrossFadeQueued("animation2");
animation.CrossFadeQueued("animation3");
anim = animation["animation3"];
}
void Update()
{
if (anim.normalizedTime == 1.0f)
{
Debug.Log("animation3 is finished");
}
}
nomalizedTime starts at 0.0 at the beginning, 0.5 at the middle of the animation, and will become 1.0 when the animation is finished.
Thanks.
I want to play animation in a function , not on Start()
Example when I click a button, animaton sequence start, then show a notice when complete
Then put the code that plays the animation in a different function then call that function when you need it to.
Check this out:
I also have 3 animations but I want to play each animation on mouse click in sequence and pop-up a panel with some instructions. I am very new to unity. How can I do that?
@udit014patel Sounds like you want to do exactly the thing I am trying to do!
can you share how you did this. It might be helpful to me.
Hi, @udit014patel , I ended up using Unity’s state machine transition parameters (which belong to the animator component) and set the animation parameters in FixedUpdate(). I even got the timed hits working!
This might not make a lot of sense but I will post my code from the script where all the animations are handled:
// LuigiAnimationEvents.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using TMPro;
//using UnityEditor.Animations;
public enum ControlState { PLATFORMING, BATTLE }
public class LuigiAnimEvents : MonoBehaviour
{
public ControlState ctrlState;
Animator animator;
// BattleMenu
public GameObject battleMenu;
public Button jumpButton;
public Button fireballButton;
public Button attackButton;
public Button defendButton;
public Button itemButton;
// Anim Controllers
public RuntimeAnimatorController platformingController;
public RuntimeAnimatorController battleController;
// Targets
public Transform playerSpawnPoint;
public Transform enemySpawnPoint;
public List<Transform> meleeTargets;
public List<Transform> rangedTargets;
public List<Transform> jumpTargets;
// party
private GameObject luigiPrefab;
// flags, targets, etc.
public GameObject signal; // make visible when timed hit is active, for debug purposes
private bool doTimedHit;
private bool failedTimedHit;
private Transform target;
float lerpTime;
void Start()
{
Application.targetFrameRate = 60;
SetupControllerState();
if (ctrlState == ControlState.BATTLE)
{
InitializePrivates();
GetBattleMenu();
if (battleMenu != null) GetButtons();
else Debug.LogError("ERROR: LuigiAnimEvents::battleMenu is empty. No buttons, no battle menu.");
// Add listeners to buttons:
attackButton.onClick.AddListener(call: delegate { PhysicalAttack(); });
}
}
void InitializePrivates()
{
// set target
if (meleeTargets.Count != 0) target = meleeTargets[0];
else if (rangedTargets.Count != 0) target = rangedTargets[0];
else
{
target = enemySpawnPoint;
Debug.LogError("ERROR: target initialized to \'enemySpawnPoint\'.This should not happen. There is no enemy target in any of the Target lists.");
}
lerpTime = 0f;
doTimedHit = false;
failedTimedHit = false;
luigiPrefab = GameObject.FindGameObjectWithTag("Player"); // there is only one party member right now
signal = GameObject.Instantiate(signal, new Vector3(-2f,1.5f,7f), Quaternion.identity);
signal.SetActive(false);
}
void FixedUpdate()
{
if (animator.GetBool("boolRunToTarget") == true)
{
LerpOverTime(playerSpawnPoint.position, target.position, 0.5f);
if (lerpTime >= 0.5f)
{
animator.SetBool("boolRunToTarget", false);
lerpTime = 0f;
}
}
if (animator.GetCurrentAnimatorStateInfo(0).IsName("Run_Back_Home")) //yes, I want him to run backwards, its funny
{
LerpOverTime(target.position, playerSpawnPoint.position, 0.5f);
if (lerpTime >= 0.5f)
{
animator.SetBool("boolRunBackHome", false);
lerpTime = 0f;
}
}
// reset flags when action sequence is over
if ((failedTimedHit == true || doTimedHit == true) && animator.GetCurrentAnimatorStateInfo(0).IsName("Battle_Idle"))
{
failedTimedHit = false;
doTimedHit = false;
}
}
void Update()
{
//+----------------------------------------------------------------------------+
//| PHYSICAL ATTACK |
//+----------------------------------------------------------------------------+
if (animator.GetCurrentAnimatorStateInfo(0).IsName("First_Punch"))
{
float t = animator.GetCurrentAnimatorStateInfo(0).normalizedTime;
// handle player's timed hit input (if there is any)
if ((t >= 29f / 83f && t < 57f / 83f) && Input.GetButtonDown("Jump") && failedTimedHit == false)
{
Debug.Log("triggerTimedHit");
doTimedHit = true;
}
// prevents player from succeeding by spamming buttons:
else if (t < 29f/83f && Input.GetButtonDown("Jump"))
failedTimedHit = true;
if (t >= 41f / 83f && doTimedHit == true)
{
animator.SetBool("boolTimedHit", true);
}
// deal with timed-hit signal: (all signal debug lines work)
if (t >= 29f / 83f && t < 57f / 83f && signal.activeInHierarchy == false) signal.SetActive(true);
if (t >= 57f / 83f && signal.activeInHierarchy == true) signal.SetActive(false);
}
else if (signal.activeInHierarchy == true) signal.SetActive(false); // in case we are already out of punch animation
}
private void LerpOverTime(Vector3 start, Vector3 end, float duration)
{
if (lerpTime <= duration)
{
lerpTime += Time.deltaTime;
float percent = Mathf.Clamp01(lerpTime / duration);
transform.position = Vector3.Lerp(start, end, percent);
}
}
//+------------------------------------------------------------------------------+
//| BUTTON EVENTS / "ACTIONS" |
//+------------------------------------------------------------------------------+
// PHYSICAL ATTACK
public UnityEngine.Events.UnityAction PhysicalAttack()
{
battleMenu.SetActive(false);
if (meleeTargets.Count == 1) target = meleeTargets[0];
else target.position = Vector3.zero;
AnimTrigger("triggerPunch");
animator.SetBool("boolRunToTarget", true);
return null;
}
//+------------------------------------------------------------------------------+
//| SETUP |
//+------------------------------------------------------------------------------+
void SetupControllerState()
{
if (SceneManager.GetActiveScene().buildIndex == 1)
{
ctrlState = ControlState.PLATFORMING;
this.GetComponent<Animator>().runtimeAnimatorController = platformingController as RuntimeAnimatorController;
this.GetComponent<CharacterController>().enabled = true;
this.GetComponent<PlayerMovement>().enabled = true;
}
else if (SceneManager.GetActiveScene().buildIndex == 2)
{
ctrlState = ControlState.BATTLE;
this.GetComponent<Animator>().runtimeAnimatorController = battleController as RuntimeAnimatorController;
this.GetComponent<CharacterController>().enabled = false;
this.GetComponent<PlayerMovement>().enabled = false;
// initialize targets:
GameObject[] targets = GameObject.FindGameObjectsWithTag("Target");
foreach (GameObject obj in targets)
{
if (obj.name == "MeleeTarget") meleeTargets.Add(obj.transform);
else if (obj.name == "RangedTarget") rangedTargets.Add(obj.transform);
else if (obj.name == "JumpTarget") jumpTargets.Add(obj.transform);
else if (obj.name == "SpawnPlayer") playerSpawnPoint = obj.transform;
else if (obj.name == "SpawnEnemy") enemySpawnPoint = obj.transform;
}
}
animator = GetComponent<Animator>();
}
void GetBattleMenu()
{
GameObject[] taggedItems = GameObject.FindGameObjectsWithTag("BattleMenu");
if (taggedItems.Length != 0)
{
foreach (GameObject obj in taggedItems)
{
if (obj.name == "BattleMenu")
{
battleMenu = obj;
return;
}
}
}
Debug.LogError("ERROR: BattleMenu not found!");
}
void GetButtons()
{
jumpButton = battleMenu.transform.GetChild(0).GetChild(2).GetChild(0).GetChild(0).GetComponent<Button>();
fireballButton = battleMenu.transform.GetChild(0).GetChild(2).GetChild(0).GetChild(1).GetComponent<Button>();
attackButton = battleMenu.transform.GetChild(1).GetChild(2).GetChild(0).GetChild(0).GetComponent<Button>();
defendButton = battleMenu.transform.GetChild(2).GetChild(2).GetChild(0).GetChild(0).GetComponent<Button>();
if (battleMenu.transform.GetChild(3).GetChild(2).GetChild(0).childCount == 0)
{
itemButton = null; //takes care of case where there are no items
}
else
battleMenu.transform.GetChild(3).GetChild(2).GetChild(0).GetChild(0).GetComponent<Button>();
}
void TestButtons()
{
Debug.Log(jumpButton.GetComponentInChildren<TMP_Text>().text);
Debug.Log(fireballButton.GetComponentInChildren<TMP_Text>().text);
Debug.Log(attackButton.GetComponentInChildren<TMP_Text>().text);
Debug.Log(defendButton.GetComponentInChildren<TMP_Text>().text);
if (itemButton != null) Debug.Log(itemButton.GetComponentInChildren<TMP_Text>().text);
else Debug.LogError("ERROR: LuigiAnimationEvents::itemButton = null. No item means no item button.");
}
// this is not being used yet, used to make sure all triggers get reset when a new one is activated.
void AnimTrigger(string triggerName)
{
foreach (AnimatorControllerParameter p in animator.parameters)
if (p.type == AnimatorControllerParameterType.Trigger)
animator.ResetTrigger(p.name);
animator.SetTrigger(triggerName);
}
}
There’s a little bit here that isn’t relevant to you, but I don’t want to pick through it right now. I will say that the “signal” object is just that red box in the GIF that appears whenever the timed-hit window opens. It is coded separately from the part that reads user input so if the user is spamming buttons before the window opens and gets “locked-out” of the timed-hit window, the box appears anyway.
I do the checks for user input in Update() so there are more checks between the limited animation frames and the user has a better chance of their input being read. Think about it: If the animations run on FixedUpdate and I set the framerate to 60fps, then nothing in that loop is getting processed faster than once every 60th of a second. That sounds fast (and it is) but if the user does a button input during the FIRST or LAST possible moment, I don’t want it getting ignored because it happened in the first 0.9/60th of a second or the last 0.9/60ths of a second–get it? More checks will be done in Update() because Update() runs as quickly as the computer’s processor can handle.
In order to manually set the framerate, you have to turn off Vsync either in the script or in the Player preferences (the Edit–>Preferences–>Player)
You can ask me more questions on my discord server:
TheDisorganization#5589
Good luck!