How to start more than one animation with unity animator? - c#

I have an animatormanager able to create run time animation from 1 animation (start frame, end frame and delay then the speed is calculated, will do morphing and more later). My unity version is 5.3.1f1
Everything work fine, some stuff are not done but are optional. The animator can queue requested animation to prevent interupting the current one.
The thing is, I can only start 1 time an animation with an object. The code to launch the next animation is exactly the same but there is just nothing to do.
Event if its 2 time the same animation with the same animation data
All formula are good and tested.
I did intensive debugging with breakpoint to make sure everything is fine at any point
Is there something to prevent me from starting an animation 2 time on an object one after an other?. I have no error or warning at all. The first animation work fine no matter the settings I put in my AnimData struct, but the second time, nothing happen.
Here is the essential:
public int? startAnim(int index)
{
if(index < animIndex.Count)
{
startAnim(animIndex[index]);
}
return null;
}
//private because the struct is internal, this make sure the animator keep control of the list.
private int? startAnim(AnimData animD)
{
if(locked)
{
#if UNITY_EDITOR
Debug.Log("anim locked");
#endif
return null;
}
//current anim (queue anim) not finished
if(endTime > Time.time)
{
if(canQueue)
{
addToQueue(animD);
return animDataQ.Count;
}
else
{
return null;
}
}
else
{
endTime = Time.time + animD.TotalTime;
StartCoroutine(animManager(animD));
return 0;
}
return null;
}
#endregion anim starters
private IEnumerator animManager(AnimData animData)
{
animator.speed = Mathf.Abs(animData.calculateSpeed(animLength, AnimType.main, currentKey).Value);
//animator.Play(0,0, animData.StartKey/animLength);
if(animData.AnimSpeed > 0)
{
animator.Play(0,0, animData.StartKey/animLength/2);
}
else
{
//animator.Play(0,0, (animLength * 2) - (animData.StartKey/animLength));
animator.Play(0,0, (((animLength*2) - animData.StartKey)/(animLength * 2)));
}
//animator.Play(0,0, (animData.AnimSpeed > 0) ? animData.StartKey/animLength : ((animLength * 2) - (animData.StartKey/animLength)));
yield return new WaitForSeconds(animData.Delay);
animator.Stop();
yield return null;
endAnim();
}
private void addToQueue(AnimData animD)
{
animDataQ.Enqueue(animD);
endTime += animD.TotalTime;
queueTime += animD.TotalTime;
}
private void endAnim()
{
if(canQueue && animDataQ.Count > 0)
{
StartCoroutine(animManager(animDataQ.Dequeue()));
}
}
Thanks for your time.

i found a work around.
I dont want to mark it as an accepted solution since its not "clean" in my opinion
there is certainly some kind of problem with the animator for some specific case like mine.
my solution was to use animator.speed = 0 instead of animator.stop();
everything worked instantly with that small change
i will ask my question to the unity forum because there is something strange for sure

Related

Attack speed messing up animation

if (Input.GetMouseButtonDown(0))
{
if (currentanim >= 3)
{
currentanim = 1;
}
if (swordanimactive == false)
{
if (swordblockanimactive == 1)
{
currentanim = currentanim + 1;
//the problem seems to be here
Swordcontroller.speed = attackspeed;
Swordcontroller.SetInteger("attackindex", currentanim);
Swordcontroller.SetTrigger("attack");
}
}
}
The problem is when I set the attack speed over 1 (regular speed), after a few clicks it just stops working. I have no idea what causes this and I don't know how to fix it since I just implemnted this new animation system. Also, the current animation is just because there are 3 sword animations. However, if I set it to only one animation it works perfectly fine. When I use 3 different animations it doesn't work.
First of all, I do not really know how you set up the animator, but I recommend this structure.
Multi Animation Animator
Has Exit Time
Maybe you can create some dataType like
public class MeleeAttackInstance
{
public string animationName;
public float animationSpeed;
public float modifiedSpeed;
public void ApplySpeedModifier(int queCount)
{
animationSpeed*(queCount +1);
}
}
And make a queue of this attacks. When craracter should attack, you place item in queue and get the top one applying modified speed.

Game slows down when printing text

I have implemented the dialog system from this Brackeys video in my project. Everything works perfectly, but when I have done the build for android I have seen that printing long texts slows the game down.
The code divides the sentence to be displayed in the UI into an array of characters and then prints each character one by one, with a small delay. I have been doing several tests and first I thought that the problem was the coroutine from where the characters were printed. But then I have removed the code from the coroutine and I have seen that the more characters it prints, the more the game slows down.
void FixedUpdate()
{
if(typeSentence)
{
if(t <= 0)
{
TypeChar();
t = charDelay;
}
t -= Time.fixedDeltaTime;
}
}
private void TypeChar()
{
GUIs[dialogue.UIIndex].dialogueText.text += charSentence[sentenceIndex];
sentenceIndex++;
if (sentenceIndex >= charSentence.Length)
{
typeSentence = false;
sentenceIndex = 0;
GUIs[dialogue.UIIndex].continueButton.SetActive(true);
}
}
I don't know if there is a more efficient way to do it, or if someone can explain to me what is happening and why it slows down so much.
Instead of triggering TypeChar() method in fixed update, you can convert TypeChar() to a Coroutine that can handle time more performing way.
private IEnumerator TypeChar()
{
while(sentenceIndex >= charSentence.Length)
{
if(typeSentence)
{
GUIs[dialogue.UIIndex].dialogueText.text += charSentence[sentenceIndex];
sentenceIndex++;
yield return new WaitForSeconds(charDelay);
}
}
typeSentence = false;
sentenceIndex = 0;
GUIs[dialogue.UIIndex].continueButton.SetActive(true);
}
And you can delete typeSentence variable completely if you do not change it out of scope.
And you can call it at Start instead of FixedUpdate.
private void Start()
{
StartCoroutine(nameof(TypeChar));
}

Question On Sequential Attacks / Finishing Sequence Early

I'm working on a top down game where when you press the attack button multiple times there's a 3 hit sequence. I have the 3 hit sequence working just fine, but I have two problems I can't seem to find a solution to through my google fu.
When the sequence is finished, I don't have a good way to transition back to the idle state.
When you press the button once or twice, I'm not sure how I can tell whether you haven't pressed the button again in a long enough time to decide the sequence has been cancelled.
(I'm using a state machine I made for the character controller, and the states don't inherit from monobehavior so I don't have access to coroutines in this attack state)
Here is the code, I would really appreciate some feedback and help.
private string currentAttack = "Attack1";
public AttackingState(Character character, StateMachine stateMachine)
: base(character, stateMachine)
{
}
public override void Action()
{
if (Input.GetKeyDown(KeyCode.Space))
{
switch (currentAttack)
{
case "Attack1":
{
this.character.SetTrigger("Attack2");
this.currentAttack = "Attack2";
break;
}
case "Attack2":
{
this.character.SetTrigger("Attack3");
this.currentAttack = "Attack3";
break;
}
case "Attack3":
{
this.character.SetTrigger("Attack1");
this.currentAttack = "Attack1";
break;
}
default:
break;
}
}
if (Input.GetAxis("Horizontal") != 0 || Input.GetAxis("Vertical") != 0)
{
character.SetTrigger("AttackCancelled");
stateMachine.SetState<WalkingState>(new WalkingState(character, stateMachine));
currentAttack = "Attack1";
}
}
public override void OnStateEnter()
{
base.OnStateEnter();
character.SetTrigger("Attack1");
}
you could track a timestamp since the last press and do something like
// stores the last attack time
private float lastAttackTime;
// whereever you want to assign this value from
// The delay before the attack is reset and starts over from 1
private const float MAX_ATTACK_INTERVAL = 1f;
if (Input.GetKeyDown(KeyCode.Space))
{
// Time.time is the time in seconds since the app was started
if(Time.time - lastAttackTime > MAX_ATTACK_INTERVAL)
{
// start over
// mimics the attack3 since there you start over at 1
currentAttack = "Attack3";
}
lastAttackTime = Time.time;
switch (currentAttack)
{
...
}
}
In general instead of string I would recommend either a simple int or an enum instead.

Update void doesnt work for me, what am I missing?

I am making an app for a school project, and am working with showing/hiding images to make an 'upgrade' and 'downgrade' button. I have made it so when you press a button, it updates the image. At first I had this with structuring my code differently. The issue with that was the fact it wouldnt load my current picture state in again after I left and re-entered the scene (although it would save the value of the upgradelevel). When I tried to restructure it, nothing works. What am I doing wrong here (if it can even be done like this...)?
I had structured the code differently at first, but came to the issue of my image not loading when I re-entered the scene. Structuring the code like this I felt it would bypass that issue. Now another issue has come up.
public GameObject woodWeapon;
public GameObject stoneWeapon;
public GameObject metalWeapon;
public static int weaponLevel = 0;
public void upgradeWeapon()
{
weaponLevel += 1;
if (weaponLevel > 3)
{
weaponLevel = 3;
}
}
public void downgradeWeapon()
{
weaponLevel -= 1;
if (weaponLevel < 0)
{
weaponLevel = 0;
}
}
public void Update()
{
if (weaponLevel == 0)
{
woodWeapon.SetActive(false);
stoneWeapon.SetActive(false);
metalWeapon.SetActive(false);
}
else if (weaponLevel == 1)
{
woodWeapon.SetActive(true);
stoneWeapon.SetActive(false);
metalWeapon.SetActive(false);
}
else if (weaponLevel == 2)
{
woodWeapon.SetActive(false);
stoneWeapon.SetActive(true);
metalWeapon.SetActive(false);
}
else if (weaponLevel == 3)
{
woodWeapon.SetActive(false);
stoneWeapon.SetActive(false);
metalWeapon.SetActive(true);
}
}
Expected result: Pressing 'upgrade' button updates my screen with am image, a wood sword instead of nothing, a stone sword instead of a wood sword, etc...
pressing 'downgrade' should do the same thing, but the other way around
Reality: Pressing either 'upgrade' or 'downgrade' does nothing to the image.

How to make cursor dissapear after inactivity in Unity

My goal is simple, like a lot of applications, I want to make my cursor invisible after a certain time of inactivity of my user.
My solution doesn't seem really optimized, here's the algorithm :
void Start {
Timer t = new Timer(5000);//Create a timer of 5 second
t.start();
}
void Update {
if(MouseMoved){
t.reset();
}
timeLeft = t.deltaTime;
if ( timeLeft == 5000 )
{
Cursor.visible = false;
}
}
I really don't like to check at every frame if the mouse is moved, I'd prefer that my mouse moved trigger something, but I'm lost here, is anyone have a better solution ?
This should be the good way to achieve the task, using coroutine, without any memory issue:
private Coroutine co_HideCursor;
void Update()
{
if (Input.GetAxis("Mouse X") == 0 && (Input.GetAxis("Mouse Y") == 0))
{
if (co_HideCursor == null)
{
co_HideCursor = StartCoroutine(HideCursor());
}
}
else
{
if (co_HideCursor != null)
{
StopCoroutine(co_HideCursor);
co_HideCursor = null;
Cursor.visible = true;
}
}
}
private IEnumerator HideCursor()
{
yield return new WaitForSeconds(3);
Cursor.visible = false;
}
There are two ways to check for things like this outside of the update loop, that I know of:
1 - Custom Events.
Writing your own custom event would allow you to call something like "OnMouseMove()" outside of the update function and it would only execute when the mouse cursor's position changes.
2 - Coroutines
Creating a separate coroutine would allow you to perform this check less frequently. To do this, you make an IEnumerator and put your mouse movement logic in there. Something like:
IEnumerator MouseMovement()
{
while(true)
{
if(MouseMoved)
{
//Do stuff
}
yield return new WaitForSeconds(1f);
}
}
That coroutine would perform the check once every second while it is running. You would start the coroutine by saying:
StartCoroutine(MouseMovement());
And to stop it, you call
StopCoroutine(MouseMovement());
If you Start it when the timer reaches 0 and stop it when the cursor is moved, you can also prevent the coroutine from running all the time, only using it while the cursor is inactive.

Categories

Resources