- Home /
2D Combo Attack
In my game, I have two problems: 1. When my character attacks while moving, he continues to move while the punch animation plays. 2. I don't know where to start with coding a combo attack.
I need it so that my character remains still after pressing the attack button and that I can perform a quick three-punch combo.
Here's my code.
public float playerSpeed, jumpForce;
public bool grounded = false;
private bool attacking;
public Transform groundedEnd;
Animator anim;
void Start(){
anim = GetComponent<Animator> ();
}
// Update is called once per frame
void Update () {
PlayerMove ();
Raycasting ();
attackInput ();
}
void FixedUpdate(){
handleAttacks ();
resetValues ();
}
//attacking
private void handleAttacks (){
if (attacking) {
anim.SetTrigger ("first_punch");
}
}
//attack inputs
private void attackInput(){
if(Input.GetKeyDown(KeyCode.Q)){
attacking = true;
}
}
//player movement function
void PlayerMove (){
anim.SetFloat ("speed", Mathf.Abs(Input.GetAxisRaw("Horizontal")));
//move right
if(Input.GetAxisRaw ("Horizontal") > 0){
transform.Translate(Vector3.right * playerSpeed * Time.deltaTime);
transform.eulerAngles = new Vector2 (0, 0);
}
//move left
if(Input.GetAxisRaw ("Horizontal") < 0){
transform.Translate(Vector3.right * playerSpeed * Time.deltaTime);
transform.eulerAngles = new Vector2 (0, 180);
}
//jump
if(Input.GetKeyDown (KeyCode.Space) && (grounded == true)){
GetComponent<Rigidbody2D> ().AddForce (Vector2.up * jumpForce);
}
}
//check if player is grounded
void Raycasting(){
Debug.DrawLine (this.transform.position, groundedEnd.position, Color.green);
grounded = Physics2D.Linecast (this.transform.position, groundedEnd.position, 1 << LayerMask.NameToLayer("Ground"));
}
private void resetValues(){
attacking = false;
}
}
Answer by Thorlar · Jul 23, 2018 at 11:11 PM
Before explaining in detail:
In essence, you want to stop your character's movement right when you attack.When my character attacks while moving, he continues to move while the punch animation plays.
I don't know where to start with coding a combo attack.
First of all, you will need an int variable, to store the combo. Increasing by one for each attack, and going to 0 when no attack is detected for X seconds. In this way, you will know where in the combo you are, and what to do (simple if check on the variable), probably name it something like comboCounter
Alternatively, you can expand your trigger design:
//attacking
private void handleAttacks (){
if (attacking) {
anim.SetTrigger ("first_punch");
}
}//didn't change a line, no need to compare, just highlighting that you will be relying on "anim" variable a lot if you go for combos this way
but it will get messy if you want to have big combos, with many outcomes, because you have a lot of dependency to the animator.
Now, on how to stop movement on above script in detail:
First of all, I do not know how your movement works definitely, but I will assume that movement combines both transform and animation (that anim.SetFloat doesn't say much alone)
//player movement function
void PlayerMove (){
if (attacking == true)
return;
anim.SetFloat ("speed", Mathf.Abs(Input.GetAxisRaw("Horizontal")));
However, there is a risk. You reset your value on fixed update. attacking bool should be true for the duration of the attack (and animation) so I don't think you should reset the value on FixedUpdate(), otherwise you will have to replace the variable I used, with another "isAttacking" boolean which will be messy. attacking bool should become false only when animation/punch has finished, not right after it has set the values you want (in this case, the Trigger) because in this case, your code could become something like this:
//attacking
private void handleAttacks (){
}
//attack inputs
private void attackInput(){
if(Input.GetKeyDown(KeyCode.Q)){
anim.SetTrigger ("first_punch");
}
}
Off-topic: If you are dealing with hitboxes, I would suggest you remake the PlayerMove(), so it works only on FixedUpdate, and move with a rigidbody.(Noting the Jump method/function on PlayerMove(), adding force on Update() isn't as performant as doing it on FixedUpdate() and I don't see why not move the rigidbody by changing its position or velocity instead of transform, since physics don't go well with Transform.translate)
[]
I sincerely hope my answer will be useful, although I did delve into "alternatives" which kinda goes against your current code and I apologize for that, it's just that in the long-term such code may backfire but all in all, good luck :)
(Don't hesitate to write more if my answer failed)
Sorry for the late reply. I had a class and when I got off, it was really late.
I easily fixed the Player$$anonymous$$ove() function and called it in FixedUpdate() ins$$anonymous$$d of Update().
I understand the idea of using the combo counter to trigger each subsequent attack, but using time in seconds to deter$$anonymous$$e when the next punch can be thrown is where I'm stumped. Could you go into a little more detail in how to approach something like that? Is it more in the code or in the animator (Sorry, I'm relatively new to coding. I only have very basic knowledge of it).
Here is my new code now that I've fixed Player$$anonymous$$ove(). public class playerController : $$anonymous$$onoBehaviour {
public float playerSpeed, jumpForce;
private float moveX;
private int comboCount = 0;
public bool grounded = false;
private bool attacking, faceRight;
public Transform groundedEnd;
Animator anim;
void Start(){
anim = GetComponent<Animator> ();
}
// Update is called once per frame
void Update () {
Raycasting ();
attackInput ();
}
void FixedUpdate(){
Player$$anonymous$$ove ();
handleAttacks ();
resetValues ();
}
//attacking
private void handleAttacks (){
if (attacking) {
anim.SetTrigger ("punch");
}
}
//attack inputs
private void attackInput(){
if(Input.Get$$anonymous$$eyDown($$anonymous$$eyCode.Q)){
attacking = true;
}
}
//player movement function
void Player$$anonymous$$ove (){
moveX = Input.GetAxis ("Horizontal");
GetComponent<Rigidbody2D> ().velocity = new Vector2(moveX * playerSpeed, GetComponent<Rigidbody2D> ().velocity.y);
//in animator, when speed is above 0.01, play running animation
anim.SetFloat ("speed", $$anonymous$$athf.Abs(Input.GetAxis("Horizontal")));
//move right
if(moveX < 0.0f && faceRight == false){
flipPlayer ();
}
//move left
if(moveX > 0.0f && faceRight == true){
flipPlayer ();
}
//jump
if(Input.Get$$anonymous$$eyDown ($$anonymous$$eyCode.Space) && (grounded == true)){
GetComponent<Rigidbody2D> ().AddForce (Vector2.up * jumpForce);
}
}
//check if player is grounded
void Raycasting(){
Debug.DrawLine (this.transform.position, groundedEnd.position, Color.green);
grounded = Physics2D.Linecast (this.transform.position, groundedEnd.position, 1 << Layer$$anonymous$$ask.NameToLayer("Ground"));
}
void flipPlayer(){
faceRight = !faceRight;
Vector2 localScale = gameObject.transform.localScale;
localScale.x *= -1;
transform.localScale = localScale;
}
private void resetValues(){
attacking = false;
}
}
Also, I'm still lost on stopping the character's motion when the attack animation plays.
No worries on the coding level, no one is judging you, but to go back on topic: You must distinquish what animator and your code does, aka what responsibility each one gets. Otherwise, you will be dealing and evaluating the animator more than what you would like in the long-term. The new code is much better, but to go to your 2 points:Could you go into a little more detail in how to approach something like that? Is it more in the code or in the animator (Sorry, I'm relatively new to coding. I only have very basic knowledge of it).
You must create a simple timer, to check when your punch finishes, so your code needs this part: (I honestly don't know why stack overflow shows the example code below like this, but do paste it somewhere else so it's readable)but using time in seconds to deter$$anonymous$$e when the next punch can be thrown is where I'm stumped. Could you go into a little more detail in how to approach something like that?
float timeElapsed = 0f; public float punchDuration;//Assign this via Inspector, it's how long you expect a punch to last from start to end
void Update() { timeElapsed += Time.deltaTime; if ( timeElapsed > punchDuration ) EndPunch(); }
void EndPunch() { attacking = false; }
Perhaps have 2 timers, the other one counting the combo, so attacking can be true/false, but the combo can still have sometime before it ends. Or, if another punch is detected while attacking == true, await for current punch to end, and go for the next one instantly. (buffering the attacks, I don't like this one so much) So, on the code I suggest, it would look something like this:
The final code is more than 3000 characters but here it is: https://pastebin.com/YkZB50uq
Enjoy :D
It's starting to work the way I would like it to. The only problem right now is that it plays the punch animation twice. Sorry for not explaining the handleAttacks() function, but it triggers the "punch" parameter in my animator which is the only condition for transitioning from Idle or Running to Punch. $$anonymous$$y character stops to punch while running, but whether he is idle or running, the punch plays twice after pressing once.
I'll also need to work the code into the animation of the subsequent punches. I'm assu$$anonymous$$g a way to do this is to create a new variable in the animator that functions similarly to the combo counter, and resets either at the end of the combo or when the punch timer runs out so that the character can return to his idle animation.
Thank you so much btw you've been a huge help!
Your answer
Follow this Question
Related Questions
Animation Parameters Not Working Correctly 0 Answers
Character clips into the ground in 2D platformer 0 Answers
Animation Coordinates Issue 0 Answers