I'm developing a 2d game in Unity and I ran into a problem. When trying to create animation transitions for one of my animations for some reason Unity instantly or very quickly deactivates my animation(like less then a second after I activated it). However when I don't have a transition conected to the animation the animation works fine. However I need to be able to transition the gameobject back to the state it was in before the animation after a certain condtion is met and so I cant leave the animation running. Does anyone know how I can solve this problem?
Ive tried switching the way I activate the animations to see if it might be related to this by trying both a bool and trigger as the condition that determines when a transition should happen but the same problem arrises
THis is a rough outline of how I activate the animation
public class InteractControl : MonoBehaviour, IPooledObject
{
public static float timeLeft = 10f;
public void OnObjectSpawn()
{
anim = GetComponent<Animator>();
}
// Change this method to just be a temporary obstacle
void Update()
{
timeLeft -= Time.deltaTime;
if (timeLeft > 0)
{
anim.enabled = true;
}
if (timeLeft < 0)
{
anim.SetTrigger("FollowAd");
}
}
}
Ad Follow with transition:
Ad Follow without transition:
Transition Settings:
The animation should activate if timeLeft is bigger than 0 and deactivate as soon as it is smaller then 0 and return the the gameobject to the state it was in before the animation started. Right now if Ad Follow_ has no transition the animation works when called upon but doesn't deactivate. if Ad Follow_ has an empty state connected to it to which it should transition should
anim.SetTrigger("FollowAd");
be called like shown above it works for less then a second or not at all and then switches straight back to the default state. It should only switch back when timeLeft < 0.
You can create a public RuntimeAnimatorController animCon for your required animation. When your conditions are met. You can do anim.runtimeanimatorcontroller = animCon;.
And When the condition is false do this anim.runtimeanimatorcontroller = null;. Its not the best way but its quick and better then most.
Related
I am trying to create a procedural gun controller, but I can't find why my gun behaves so weird when I change the fire mod to automatic. To understand this better, I will give an example: when I try to shoot by pressing the "Mouse0" key, on semi auto mod it works fine, it behaves like I want it to (the bolt kicks back when I shoot, the front barrel kicks back - I animate them with code instead of frame animations from Unity or a 3rd party soft like Blender), but..., when I change to automatic, the problem is not that I get errors, The animations of the moving pieces don't work as they should and are not triggered correctly.
I tried to use different methods for shooting(using the WaitForSeconds() and WaitForSecondsRealtime() while using coroutines). It didn't work. I tried using the time function with scaled and unscaled time in the update function. I still got the same results.
What should I do to?
This is the shoot function untill now:
void GunFireManagement()
{
if (fireType == fireMethod.single)
{
foreach (BlowBack anim in animations)
{
if (Input.GetKeyDown(KeyCode.Mouse0))
{
gunSoundClone = Instantiate(gunShootSound, this.transform.position, Quaternion.identity) as GameObject;
anim.piece.transform.position = anim.kickbackState.transform.position;
}
if (anim.piece.transform.position != anim.initialPosition.transform.position)
{
anim.piece.transform.position = Vector3.Lerp(anim.piece.transform.position, anim.initialPosition.transform.position, anim.speed);
}
Destroy(gunSoundClone, 0.5f);
}
}
if (fireType == fireMethod.auto)
{
foreach (BlowBack anim in animations)
{
if (Input.GetKey(KeyCode.Mouse0) && Time.time - lastFired > 1f/autoFireRate)
{
lastFired = Time.time;
gunSoundClone = Instantiate(gunShootSound, this.transform.position, Quaternion.identity) as GameObject;
anim.piece.transform.position = anim.kickbackState.transform.position;
}
if (anim.piece.transform.position != anim.initialPosition.transform.position)
{
anim.piece.transform.position = Vector3.Lerp(anim.piece.transform.position, anim.initialPosition.transform.position, anim.speed);
}
Destroy(gunSoundClone, 0.5f);
}
}
}
The issue is how you are using Vector3.Lerp. The first two arguments you pass to the method are supposed to be the start and end positions of the animation, and the third one, t, is supposed to be the progress of the animation from the start towards the end, as a value between 0 and 1.
You can calculate the value of t by dividing the time since the shot started with the duration of the animation. For example if the length of the animation is 2 seconds, and the short started 1 second ago, then t should be 0.5.
if(isFiring)
{
float timeSinceShotStart = Time.deltatime - lastFired;
// t = s / v
float animationDuration = 1f / anim.speed;
UpdateBlockBackAnimationState(timeSinceShotStart / animationDuration);
}
}
private void SetBlowBackAnimationState(float progress01)
{
foreach(BlowBack anim in animations)
{
Vector3 initialPosition = anim.initialPosition.transform.position;
Vector3 finalPosition = anim.finalPosition.transform.position;
anim.piece.transform.position = Vector3.Lerp(initialPosition, finalPosition, progress01);
}
}
I recommend you try to split up your code into smaller methods that are easier to understand. You are trying to do so many things in one generic "GunFireManagement" method that it becomes pretty difficult to keep track of all the different pieces :)
I also recommend considering using a tweening framework such as DOTween to make it easier to animate things over time.
This is a beginner question that should be available from the first google result but I can't find it.
I have this method that plays an animation using an Animator:
IEnumerator popDownAnimation(string c)
{
animator = GetComponent<Animator>();
animator.Play("Pop Down Animation");
yield return new WaitUntil(() => animator.GetCurrentAnimatorStateInfo(0).normalizedTime > 1 && !animator.IsInTransition(0));
animator.Play("New State");
}
Can't remember where I got it from, but yield return new WaitUntil(() => animator.GetCurrentAnimatorStateInfo(0).normalizedTime > 1 && !animator.IsInTransition(0)); isn't working. It's instantly jumping from "Pop Down Animation" to "New State", so the "Pop Down Animation" doesn't have a chance to play.
Try this
IEnumerator popDownAnimation(string c)
{
animator = GetComponent<Animator>();
animator.Play("Pop Down Animation");
float animationLength = animator.GetCurrentAnimatorStateInfo(0).length;
yield return new WaitForSecondsRealtime(animationLength);
animator.Play("New State");
}
However, like #NotVeryGoodAtThis mentioned, I wouldn't do this in code unless you have a specific reason why you can't make it work using animation events or using parameters within the animator to transition between animation states. Designing your animation using Unity's Animator window and using states, parameters, and transitions will be easier to manage than doing it through code, especially as your animators become more complicated.
I would suggest using Animation events to do whatever you need. Just remember to make a public method and add the animation event at the end of your animation.
I wanted my UI to not resize when user is still resizing the game (holding click in the window border) and only when the user has released the mouse the resize event will trigger.
I have tried to achieve it on Unity but so far I only able to detect windows size change, which my script checked every 0.5 second and if detected change it will resize the UI. But of course resizing everything caused a heavy lag, so resizing every 0.5 second is not a good option but resizing every 1 second is not a good idea either because 1 second is considered too long.
The question might be too broad but I have specified the problem as small as possible, how do I detect if user is still resizing the window? And how do I detect if user has stopped resizing the window (stop holding click at window border)?
You can't tell when someone stops dragging a window, unless you want to code a low level solution for ever desktop environment and every operating system.
Here's what worked for me with any MonoBehavior class, using the OnRectTransformDimensionsChange event:
public class StackOverflow : MonoBehaviour
{
private const float TimeBetweenScreenChangeCalculations = 0.5f;
private float _lastScreenChangeCalculationTime = 0;
private void Awake()
{
_lastScreenChangeCalculationTime = Time.time;
}
private void OnRectTransformDimensionsChange()
{
if (Time.time - _lastScreenChangeCalculationTime < TimeBetweenScreenChangeCalculations)
return;
_lastScreenChangeCalculationTime = Time.time;
Debug.Log($"Window dimensions changed to {Screen.width}x{Screen.height}");
}
}
I have some good news - sort of.
When the user resizes the window on Mac or PC,
Unity will AUTOMATICALLY re-layout everything.
BUT in fact ONLY when the user is "finished" resizing the Mac/PC window.
I believe that is what the OP is asking for - so the good news, what the OP is asking for is quite automatic.
However. A huge problem in Unity is Unity does not smoothly resize elements as the user is dragging the mouse to expand the Mac/PC window.
I have never found a solution to that problem. (A poor solution often mentioned is to check the size of the window every frame; that seems to be about the only approach.)
Again interestingly, what the OP mentions
" ..and if window has stopped resizing .."
is automatically done in Unity; in fact do nothing to achieve that.
I needed something like this for re generating a line chart, but as it has too many elements, it would be heavy to do it on every update, so I came up with this, which for me worked well:
public class ToRunOnResize : MonoBehaviour
{
private float screenWidth;
private bool screenStartedResizing = false;
private int updateCounter = 0;
private int numberOfUpdatesToRunXFunction = 15; // The number of frames you want your function to run after, (usually 60 per second, so 15 would be .25 seconds)
void Start()
{
screenWidth = Screen.width; // Identifies the screen width
}
private void Update()
{
if (Screen.width != screenWidth) // This will be run and repeated while you resize your screen
{
updateCounter = 0; // This will set 0 on every update, so the counter is reset until you release the resizing.
screenStartedResizing = true; // This lets the application know when to start counting the # of updates after you stopped resizing.
screenWidth = Screen.width;
}
if (screenStartedResizing)
{
updateCounter += 1; // This will count the updates until it gets to the numberOfUpdatesToRunXFunction
}
if (updateCounter == numberOfUpdatesToRunXFunction && screenStartedResizing)
{ // Finally we make the counter stop and run the code, in my case I use it for re-rendering a line chart.
screenStartedResizing = false;
// my re-rendering code...
// my re-rendering code...
}
}
}
Unity 2019.3.7f1
..
The artist I'm working with has made some animations for some UI stuff using the mecanim system Animator component.
I fire the animation with
Animator.SetTrigger("ParamaterName")
The gameobjects with the animator components, which are being animated, can get disabled by some of my scripts.
This can happen while the animation is playing..
So if the animation starts playing, and lets say the animation has made a button get bigger, then the gameobject gets disabled. When the gameobject is re-enabled (the animation is no longer playing) it is still big..
Is there a way to tell the Animator to go back to normal?
I have tried stuff like this in onEnable and OnDisable in a script on the GameObject
Animator.keepAnimatorControllerStateOnDisable = false
Animator.Play("normalState",0,0f);
Animator.playbackTime = 0f;
Animator.Update(0f);
This mecannim thing just seems like a black box as to how it works. I'm not familiar with it as I've always just used my own library to animate stuff that I've been using for donkeys years.
EDIT:
This was the solution...
private void Start()
{
Animator.keepAnimatorControllerStateOnDisable = true;
}
private void OnDisable()
{
Animator.Play("normalState",0,0f);
}
Make sure your keepAnimatorControllerStateOnDisable isn't set to true. Instead, you should set to false to clear the current state of the Animator controller
Animator.keepAnimatorControllerStateOnDisable = false
This way whenever you disable an object with an Animator, the Animator states and all its parameters go back to default.
Update
From your comments was able to understand the default is to have the button bigger. So, it's actually the other way around (set it to true, instead of false).
As you mention, write the following code
private void Start()
{
Animator.keepAnimatorControllerStateOnDisable = true;
}
private void OnDisable()
{
Animator.Play("normalState",0,0f);
}
private IEnumerator reviveCountdown() {
timeLeft = 5;
while (timeLeft >= 0) {
reviveAnim.ResetTrigger ("AdReviveTrigger");
reviveAnim.SetTrigger ("AdReviveTrigger");
reviveCountdownText.text = timeLeft.ToString();
yield return new WaitForSecondsRealtime(1.0f);
timeLeft--;
}
}
I'm using the above IEnumerator to show a timer that counts down from 5 to 0, and animates the text each time it counts down. The countdown works correctly. The problem I'm having is the animation IS TRIGGERED but never actually plays because it gets stuck on the animation state, as shown in the screenshot below. It sits in the AdRevive state for the entire 5 seconds but never plays. The transition from Idle is a simple trigger set in the code. The animation works if I play it manually in the Unity editor. Anyone know why it gets stuck?
I solved my own issue. The Time.timeScale was set to 0 because the game was paused during this animation, so it would never play. To fix, I set the animationUpdateMode to UnscaledTime.