Question about AnimationEvent.Time in Unity - c#

I am quite new in Unity and I have a simple question regarding Animation events.
In my code snipet I have the public variables - I noticed that I can't reference an AnimationEvent - and some simple stuff to do with them.
public GameObject completeLevelUI; // a panel
public Text endUI; // the text on the panel that show your success or fail
public AnimationEvent nextSceneEvent; // AnimatonEvent in the animation of the panel above
public void GameOver() { nextSceneEvent.time = 2; completeLevelUI.SetActive(true); endUI.text = "LEVEL\nFAILED"; }
public void GameWon() { nextSceneEvent.time = 6; completeLevelUI.SetActive(true); endUI.text = "LEVEL\nCOMPLETED"; }
public void LoadEnd() { SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex + 1); }
The AnimationEvent's target method is LoadEnd().
The default firing time is 1.5 seconds and the code won't change it.
The reason why I need different fire times can't be seen in the shared code - it is not important now. I figured out some other ways to solve it, I am just curious why it isn't working.
I have tried to change the activating and the time setting code piece but it is the same.
Somehow I have to reference the AnimationEvent? Is it a problem that this script is called multiple times, even from places where there is no AnimationEvent - I didn't receive any exceptions during my examination.
Any ideas? Thanks for helping!

I guess you have an animation playing and at the end you want to trigger the new scene.
You can use different approaches.
First, you add the animation event directly to the animation clip and you set the method in the inspector. The method has to be on a component attached to the same game object as the animation.
Second, you launch the animation and wait for the end of it to call the method:
void EndProcess()
{
StartCoroutine(EndProcessSequence());
}
IEnumerator EndProcessSequence()
{
Animation anim = GetComponent<Animation>();
anim.Play("animName");
yield return new WaitWhile(()=> anim.isPlaying);
LoadNewScene();
}
Here' s a reusable version
IEnumerator EndProcessSequence(string animName, Action onComplete)
{
Animation anim = GetComponent<Animation>();
anim.Play(animName);
yield return new WaitWhile(()=> anim.isPlaying);
onComplete?.Invoke();
}

Related

Play in sequence of 4 game objects - animators (Not single animator with multiple states) in a queue

I'm new to unity. I have 4 GameObjects say Red, Green, Blue, Yellow - with index mapped 0, 1, 2 and 3 respectively. Also, I kept index sequence in the list List<int> systemTrack.
Now, I'm calling the sequence of animator with respect to list { 0, 1, 2, 3 } (this can be random 0-3).
I can perform one animator loop at a given time.
void Start() {
animator.Play("blue_animation", 0, 0);
}
How can I call in sequence based on list ? Perhaps Update() is right place to call.
So far I found this thread - But it has single Animator object with multiple state called in sequence which is not my case.
Few others discussions also available for Animation component Not for the new Animator component.
In your current class you could use a Coroutine like
List<AnimatorQueueController> animatorQueues;
private void Start()
{
StartCoroutine(RunAllControllersSequencial());
}
IEnumerator RunAllControllersSequencial()
{
foreach (var queue in animatorQueues)
{
// This runs the routine and waits until it finishes
yield return queue.RunAnimationQueueAndWait();
}
}
Now the only thing to do is define/implement how each if these controllers "knows", that its own animation queue has finished.
There are probably many possible ways to go. Straight away: None of them will be beautiful ;) Since the Animator uses a lot of string based stuff you always will end up either having to make sure all animation/trigger names are written correctly, or you will have to reference the animationClip and either hope the state is called the same or you have to expensively find the states via the according clip, you'd have to know how much time to wait for the queue to finish, etc.
This said, also this solution won't be perfect but in my opinion for your setup it would be the most convenient ;)
You could e.g. use an Animation Event and do something like
public class AnimationQueueController : MonoBehaviour
{
[SerializeField] private Animator _animator;
// Here set the first state name for each instance via the Inspector
[SerializeField] private string firstStateName = "first_state";
private void Awake ()
{
if(!_animator) _animator = GetComponent<Animator>();
}
// Simply have another routine you can yield so it is executed and at the same time waits until it is done
public IEnumerator RunAnimationQueueAndWaitUntilEnd()
{
hasReachedEnd = false;
_animator.Play(firstStateName, 0 ,0);
yield return new WaitUntil(() => hasReachedEnd);
}
// This is the method you will invoke via the Animation Event
// See https://docs.unity3d.com/Manual/script-AnimationWindowEvent.html
public void AnimationEnd()
{
hasReachedEnd = true;
}
}
This at least reduces your implementation overhead to
make sure you fire the Animation Event at the end of the last state
make sure you provide the name of the first state in the AnimatorQueueController
in your original script instead of Animator references rather store the AnimatorQueueController in your list

how to get actual animation play position without using animator in untity?

anim = GetComponent<Animation>();
float length = anim.clip.length;
Debug.Log(length);
will show me following result:
45
If this position will be achieved, animation should start at begining again. Any ideas, how to achieve this using C# Scripting? I am not using any animator, just animation in ChildObject.
This will not work, of course, 'cause its always true:
if(length==45)
anim.Restart();
It just looks like your looking to trigger a restart at the end of the animation. So insert an AnimationEvent at the end of the animation. Look at Unity's simple Scripting API example. Ignore the fact that they are using an Animator, they just use it to get the clip. Obviously, you will already have your clip.
https://docs.unity3d.com/ScriptReference/AnimationEvent.html
Here is the modified Unity script from the link above.
public void Start()
{
// existing components on the GameObject
AnimationClip clip;
// new event created
AnimationEvent evt;
evt = new AnimationEvent();
// put some parameters on the AnimationEvent
// - call the function called PrintEvent()
// - the animation on this object lasts 2 seconds
// and the new animation created here is
// set up to happen 1.3s into the animation
evt.intParameter = 12345;
evt.time = 1.3f;
evt.functionName = "PrintEvent";
clip.AddEvent(evt);
}
// the function to be called as an event
public void PrintEvent(int i)
{
print("PrintEvent: " + i + " called at: " + Time.time);
}

Timer function doesnt work (Unity+C#)

I wrote my own function of rotating a group of objects. I want ot make a new one, with smooth rotating, so I need a timer. I have tried to call Timer a few times, but it doesnt work. Here's my code:
public class rotate_around : MonoBehaviour
{
public Transform sphere;
// Update is called once per frame
void Update ()
{
if (Input.GetKeyDown(KeyCode.RightArrow))
{
sphere.RotateAround(new Vector3(3, 3, 3), new Vector3(0, 1, 0), 45);
timer();
}
}
public IEnumerator timer()
{
yield return new WaitForSeconds(5);
// actually I tried to add Debug.Log("blahblahblah") here, but it still didnt output anything
}
}
Try using StartCoroutine(timer()); instead of just timer();
StartCoroutine("timer", true);
would be the best way to do it!
timer() would just call a normal method. IEnumerator works different.
As mentioned before, the correct way to start a coroutine is StartCoroutine(timer());
from your code it isn't clear what you are trying to achieve, currently you do nothing after waiting those five seconds
Also you probably should implement some sort of mechanism that prevents user from spamming coroutines. You can set a bool flag, or hold reference to the coroutine you started - I often do
Coroutine myRoutine; // in global scope
if (myRoutine==null) myRoutine=StartCoroutine(timer()); // in event handler
myRoutine will null itself when its finished

Slider code, doesn't work as scrubber, won't move when down?

What breaks is the scrubbing slider, it recognises a click, but it does not move. I have interactable enable.
I'm having to reimport all assets to get it to work again but even now this isn't a sure bet it'll fix it.
This code was working perfectly fine about a week ago, I've made little changes to it since.
A lot of it is commented so I can learn that different parts of the code. Feel free to delete it if it bothers you.
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
public class NewAnimController : MonoBehaviour {
public Slider scrubSlider; //This is the slider that controls the current position of the animation. Values have to be 0 to 1 for normalized time of the animation, called later in the script
public Slider speedSlider; //This is the slider that controls the playback speed of the animation
public Animator animator; //Animator that is attached to the model that we are looking to control the animation of
public float playbackSpeedAdjustment = 0.5f; //This is a variable that can be easily adjusted to change the total playback speed of the animation. If it's too fast make smaller and vice versa
public Text currentDateText;
public int monthsInProject;
private float rememberTheSpeedBecauseWeMightNeedIt; //Float needed to keep the speed of the animation between pausing and playing
public void Update()
{
float animationTime = animator.GetCurrentAnimatorStateInfo(0).normalizedTime; //float a new value animationTime that is equal to the animators animation state then converted to normalized time
//Debug.Log("animationTime (normalized) is " + animationTime); //logging the normalized time to be able to store it for the scrubbing slider. Doesn't need to be logged for this to work, good to log it to make sure that it's working
scrubSlider.value = animationTime; //making the slider value equal to the now logged normalized time of the animation state
}
public void ScrubSliderChanged(float ScrubSliderchangedValue) // this value has to be floated so that the scrubbing slider can be attached to in the inspector to be able to change the current frame of the animation
{
animator.Play("Take 001", -1, scrubSlider.normalizedValue);
}
public void SpeedSliderChanged(float SpeedSliderchangedValue) //value is floated to be able to change the speed of the animation playback
{
animator.speed = speedSlider.normalizedValue * playbackSpeedAdjustment; // here the speed is multiplied by the adjustment value set in the editor just in case the playback speed needs to be changed outside of normalized values
}
public void UserClickedPauseButton()
{
if (animator.speed > 0f)
{
// we need to pause animator.speed
rememberTheSpeedBecauseWeMightNeedIt = animator.speed;
animator.speed = 0f;
}
else
{
// we need to "unpause"
animator.speed = rememberTheSpeedBecauseWeMightNeedIt;
}
}
public void UserClickedBackButton()
{
scrubSlider.value = scrubSlider.value - (1f / monthsInProject);
}
public void UserClickedForwardButton()
{
scrubSlider.value = scrubSlider.value + (1f / monthsInProject);
}
public void UserClickedStartButton()
{
scrubSlider.value = 0;
}
public void UserClickedEndButton()
{
scrubSlider.value = 1;
}
}
Many thanks for all your help.
I'm pretty sure the problem is this. In Update you are doing something like this:
scrubSlider.value = animationTime
that means that every frame, "NO MATTER WHAT", YOU ARE SETTING THE SLIDER POSITION, to, where the animation is. If the user is trying to move the slider - you are moving it right back, that same frame!
It's not so easy to solve this problem. Unity did not include a trivial built-in function for this. You need a separate script which sits on the slider which determines whether or not the handle is being held down. You have to use the OnPointerDown and OnPointerUp handlers.
How to use pointer handlers in modern Unity:
Step one, make this small script called Teste.cs
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using System.Collections;
public class Teste:MonoBehaviour, IPointerDownHandler, IPointerUpHandler
{
public Slider theSlider;
public bool sliderIsBeingHeldDown;
public void OnPointerDown(PointerEventData data)
{
sliderIsBeingHeldDown = true;
Debug.Log("holding down!");
}
public void OnPointerUp(PointerEventData data)
{
sliderIsBeingHeldDown = false;
Debug.Log("holding up!");
}
}
Don't forget the declaration...
Teste:MonoBehaviour, IPointerDownHandler, IPointerUpHandler
rather than the usual MonoBehavior.
Drag that script on to your UI.Slider.
Note that in general these only work when they are "on" the thing in question.
Run, and try clicking up and down on the slider button - check the console. It works right? Now you have to use that variable inside NewAnimController
(1) Add a public variable to NewAnimController,
public Teste teste;
(2) Be sure to drag to connect that variable in Inspector
(3) Now you can refer to teste.sliderIsBeingHeldDown to see if that slider is being held down. So instead of doing this every frame...
scrubSlider.value = animationTime;
you will have to do this every frame...
if (teste.sliderIsBeingHeldDown)
Debug.Log("don't try to work against the user!");
else
scrubSlider.value = animationTime;
I hope that works! I think that's the simplest way to know when the slider is being held-down.
You really chose a difficult project for your first project! Hell!
It's worth noting that you could also put the slider-adjusting code (I mean to say your actual code for the scrubber) "inside" the script that is actually on the slider -- but I wouldn't worry about that for now. Your solution as stands is good.
Note depending on how you implement it, you may need to make it pause more elegantly when you hold down the slider handle, but before moving the slider handle. To achieve this, one approach is arrange to call UserClickedPauseButton() or a similar concept, when, the Down/Up routines shown here in "Teste", are called. Conversely you could just not bother with ScrubSliderChanged, and instead do the whole job inside code that runs whenever the handle is "down".
(In general, you'd almost certainly use UnityEvent here to make a solid reusable solution for a slider such as this for use with something like a scrubber.)

Switch between multiple materials on a button press

I need to have an object in my scene change between two different materials at run time, when ever a button is pressed in my Unity project. However, I have never done this before and I'm having an issue getting my head around how to do this.
In my scene I have one game object I've called my controller. This script holds my material switching class and is looking like this:
public GameObject cupMesh;
bool isOn = true;
// Use this for initialization
void Start ()
{
cupMesh = GameObject.Find("CupMesh");
}
// Update is called once per frame
void Update ()
{
}
void OnGUI()
{
if(GUI.Button(new Rect(10,10, 100, 40), "Show mesh"))
{
renderer.enabled = false;
}
}
I know this doesn't change the material, but the above code does nothing. I've never modified anything on the mesh renderer before but I know there is a list of materials on it.
How can I access that list so I can have my program switch between the two materials found there?
To show or hide a gameObject, rather than using render.enabled property, you should use this:
// Unactivates the game object.
gameObject.SetActive (false);
However it's not clear from the code if you want to adjust the material of the object the script is attached to, or the cupMesh game object.
If you wanted to make the cupMesh disappear, you would use:
cupMesh.SetActive (false);
Or if you wanted to access the material component of the cupMesh, this is
cupMesh.renderer.material

Categories

Resources