Question On Sequential Attacks / Finishing Sequence Early - c#

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.

Related

How to simplify animation transaction

In 2D scene, I have 3 idle states and 3 walk states (each idle/walk state has 3 direction sub state).
I know I can make transactions in animator.
My question is, how to simplify transaction? For example, idle_front state has 5 transactions to other states (idle_back, idle_right, walk_front, walk_back, walk_right), now if I want to add an attack state, also with 3 direction sub state, the transaction lines makes me crazy.
My solution
I have tried using animator.Play() to play animation, but when switch to attack state, there is a problem.
The invocation of idle state play is in FixedUpdate() event function, after response to the attack key press event (to play attack animation), only little frames of attack is played, it is soon replaced by idle animation.
How can I implement similar effect of a trigger, to play the whole attack animation? Or is there any way to simplify transactions, and continue to use a trigger?
Example code is here
private void FixedUpdate()
{
if (moveInput.y == 0)
{
switch (moveInput.x)
{
case < 0:
animator.SetInteger(Direction, (int) MovingDirection.Left);
animator.Play("player_walk_right");
spriteRenderer.flipX = true;
break;
case > 0:
animator.SetInteger(Direction, (int) MovingDirection.Right);
animator.Play("player_walk_right");
spriteRenderer.flipX = false;
break;
}
}
else
{
switch (moveInput.y)
{
case < 0:
animator.SetInteger(Direction, (int) MovingDirection.Down);
animator.Play("player_walk_front");
break;
case > 0:
animator.SetInteger(Direction, (int) MovingDirection.Up);
animator.Play("player_walk_back");
break;
}
}
}
void OnFire()
{
switch (animator.GetInteger(Direction))
{
case (int) MovingDirection.Up:
animator.Play("player_attack_back");
break;
case (int) MovingDirection.Down:
animator.Play("player_attack_front");
break;
case (int) MovingDirection.Left:
animator.Play("player_attack_right");
spriteRenderer.flipX = true;
break;
case (int) MovingDirection.Right:
animator.Play("player_attack_right");
spriteRenderer.flipX = false;
break;
}
}
You just need to stop moving while the attack animation is playing. To know when the animation is end, one way is using events https://docs.unity3d.com/Manual/AnimationEventsOnImportedClips.html
First you need to add an event near the end of the animation, let's name it OnAttackEnd.
NOTE! To ensure the script can receive the event, the script and the animator must be attached on the same GameObject.
In the script add a boolean field to indicate if attack anim is playing and the OnAttackEnd callback.
bool _isAttackAnimPlaying;
void OnAttackEnd()
=> _isAttackAnimPlaying = false;
In OnFire method, set the boolean field to true, and in FixedUpdate method check the value.
void OnFire()
{
switch (animator.GetInteger(Direction))
{
case (int) MovingDirection.Up:
animator.Play("player_attack_back");
_isAttackAnimPlaying = true;
....
}
}
private void FixedUpdate()
{
if(!_isAttackAnimPlaying)
{
.....
}
}

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.

How to call a method after each line of code or something like that

I have a method that is supposed to check a player's HP and then perform some logic based on if the number is greater than 0, or less than or equal to 0.
The method works but if I type it in the code and then change the hp value of a player it won't do anything until I type in the method name again. Then it will display the correct information.
At first I thought of using some kind of loop instead of a method. If I am correct, that means I'd have to have put curly braces around all my code that needs to get checked (which means basically around the whole code and I don't want that). Same with IF statement - even if I put it at the beginning of the code I'd still have to put curly braces around all the code.
Then I thought of a method which I already mentioned. And as I said - it does what it's supposed to do but only if I paste it multiple times into the main code.
Now, my question - is there ANY way to make the method "repeat itself" constantly or, I don't know, start at some place in the code and remain active?
Here is the method:
static void Status()
{
if (Player.playerHealth <= 0)
{
Player.isDead = true;
Console.WriteLine("You are dead!");
}
else if(Player.playerHealth > 0)
{
Player.isDead = false;
Console.WriteLine("You are not dead!");
}
}
I would be really grateful for any help.
You can define playerHealth as a property. That way, any time anyone changes it, you can make some code fire, including the check that you want.
class Player
{
protected int _playerHealth = 0;
public int PlayerHealth
{
set
{
_playerHealth = value;
if (_playerHealth == 0)
{
isDead = true;
Console.WriteLine("You are dead!");
}
}
get
{
return _playerHealth;
}
}
Now you don't even need to call Status... the logic will occur automatically whenever the player's health is modified.
var player = new Player();
player.PlayerHealth = 0; //Automatically triggers message
Correct me if I'm wrong but you want to call a method everytime the player's life changes? It looks like you should use an event.
public class HealthEventArgs : EventArgs
{
public int Health { get; set; }
public HealthEventArgs(int health)
: base()
{
this.Health = health;
}
}
public class Player
{
public event EventHandler<HealthEventArgs> LifeChanged;
int _Health;
public int Health
{
get => _Health;
set
{
_Health = value;
LifeChanged?.Invoke(this, new HealthEventArgs(_Health));
}
}
}
Then in your code it would look something like that
Player player = new Player();
player.LifeChanged += (o, e) =>
{
Player p = o as Player;
int health = e.Health;
// Whatever logic here, with access to the player and its health
};
With that mechanism in place, everytime the health of a player changes the event will fire and you will have access to the player and its health and can act accordingly.
If you're not confortable with lambda expressions, the code above can also be written as such
Player player = new Player();
player.LifeChanged += PlayerLifeChanged;
public void PlayerLifeChanged(object o, HealthEventArgs e)
{
Player p = o as Player;
int health = e.Health;
// Whatever logic here, with access to the player and its health
};

How to start more than one animation with unity animator?

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

How to add delay to shooting on keypress

I'm making a space shooter and I'm wondering how to make a delay with the shooting instead of spamming the space bar thanks :)
public void Shoot()
{
Bullets newBullet = new Bullets(Content.Load<Texture2D>("PlayerBullet"));
newBullet.velocity = new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)) * 5f;
newBullet.position = playerPosition + playerVelocity + newBullet.velocity * 5;
newBullet.isVisible = true;
if (bullets.Count() >= 0)
bullets.Add(newBullet);
}
I think you want to use XNA's GameTime property. Whenever you shoot a bullet you should record the time it happened as the current GameTime.ElapsedGameTime value.
This is a TimeSpan so you could compare it like the following:
// Create a readonly variable which specifies how often the user can shoot. Could be moved to a game settings area
private static readonly TimeSpan ShootInterval = TimeSpan.FromSeconds(1);
// Keep track of when the user last shot. Or null if they have never shot in the current session.
private TimeSpan? lastBulletShot;
protected override void Update(GameTime gameTime)
{
if (???) // input logic for detecting the spacebar goes here
{
// if we got here it means the user is trying to shoot
if (lastBulletShot == null || gameTime.ElapsedGameTime - (TimeSpan)lastBulletShot >= ShootInterval)
{
// Allow the user to shoot because he either has not shot before or it's been 1 second since the last shot.
Shoot();
}
}
}
You can have a counter that counts the number of frames since the last shot.
int shootCounter = 0;
...
if(shootCounter > 15)
{
Shoot();
shootcounter = 0;
}
else
{
shootcounter++;
}
If you want to go more advanced, you can use the gameTime that the default update method normally gives you to keep track of how much times has passed since you've last shot.

Categories

Resources