0
\$\begingroup\$

I am making a little top-down 2D RPG right now and I'm struggling with the range of my attacks. The problem is that my range is stuck on the right side of my player. This is of course really annoying, because you can't attack to the left.

I want to make a script that makes the range transform, based on where you are facing. I'm just not experienced enough to know how.

Maybe creating a script that changes the whole movement of the player, wouldn't be a bad idea. It would of course be nicer to just add a few things to fix the problem, but I would also be ok with changing the overall movement as a solution, I guess.

Here is the Movement + PlayerAttack script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;

public class Move : MonoBehaviour
{   

public int damage;
private float timeBtwAttack;
public float startTimeBtwAttack;



public float attackCooldown;
float _lastAttackTime;

public Transform attackPos;
public LayerMask whatIsEnemies;
public float attackRangeX;
public float attackRangeY;
public float ValueIsGiven;


//Move Start
[SerializeField] private Rigidbody2D rb2D;
[SerializeField] private float speed;
private Vector2 moveInputValue;

private void OnMove(InputValue value)
{
    moveInputValue = value.Get<Vector2>();
    if(ValueIsGiven >= 1){
    Debug.Log(moveInputValue);
    }
}
private void MoveLogicMethod()
{
    Vector2 result = moveInputValue * speed * Time.fixedDeltaTime;
    rb2D.velocity = result;
}
    private void FixedUpdate()
    {
        MoveLogicMethod();
    }
//Move End

//Attack Start

public void OnPlayerAttacking()
{
// If attacking too close to previous attack, ignore this input.
if (Time.time - _lastAttackTime < attackCooldown) return;


var enemiesToDamage = Physics2D.OverlapBoxAll(
           attackPos.position,
           new Vector2(attackRangeX, attackRangeY),
           0,
           whatIsEnemies
);

foreach (var enemy in enemiesToDamage) {
     enemy.GetComponent<Enemy>().TakeDamage(damage);
}

_lastAttackTime = Time.time;
}
void OnDrawGizmosSelected(){
        
        Gizmos.color = Color.black;
        Gizmos.DrawWireCube(attackPos.position, new Vector3(attackRangeX, attackRangeY, 1));
}
//Attack End
}

I don't use any rotation in my movement script.

Here is a screenshot of my scene with the range: enter image description here

The most important thing right now is to get it to work, the visuals are second.

Also the AttackRangePos is currently defined by an empty game object named "attackPos". (You can also see it in the screenshot I took). I think some people could get confused that the Sword is the attack position, but the Sword is only for visuals. Would still be essential for the sword to move too.

I also developed an idea, which could maybe deal with this problem: enter image description here The idea is basically that the attackPos gets the MoveValue, so it knows where the player is currently moving and then uses this information to move to the side of the player which is facing the direction of where the player is going. Since the attackPos is parented in the Player, the attackPos only needs to be moved by 1-2 units at max, so it shouldnt be to performance consuming, i hope.

I also want the attackPos to stay at its current position, even when you are not currently moving anymore. I would really apppreciate it, if you could help me fixing the problem that I have. To whoever this reads: Have a nice day!

@Maganta Hi, Maganta thnak you so much for you answer, I really appreciate it, but Im not sure if I used this infos right. Also I couldnt fit this in the comment section. My code is a total mess and Im kinda confused, especially because I dont get any errors, eventhough its not working. This ofc is because of me being unexpirienced and I have to admit I have no idea what I should do. Heres the code:

public class Move : MonoBehaviour
{   

public int damage;
private float timeBtwAttack;
public float startTimeBtwAttack;

public float attackRange;
public Vector2 moveInputValue;
public Vector2 attackDir = Vector2.right; // independent direction of attack

public float attackCooldown;
float _lastAttackTime;

public Transform Player;
public Transform attackPos;
public LayerMask whatIsEnemies;
public float attackRangeX;
public float attackRangeY;
public float ValueIsGiven;

//Move Start
[SerializeField] private Rigidbody2D rb2D;
[SerializeField] private float speed;



private void OnMove(InputValue value)
{
    var attackPos = (Vector2)transform.position + attackDir*attackRange;   //Get the world coordinates of attackPos
    moveInputValue = value.Get<Vector2>();
    if(moveInputValue != Vector2.zero){
    attackDir = moveInputValue.normalized; //When the player stops moving, use the last movement direction as the attack direction
    }
    if(ValueIsGiven >= 1){
    Debug.Log(moveInputValue);
    }
    }


public float Angle(Vector2 vector2)
{
return 360 - (Mathf.Atan2(vector2.x, vector2.y) * Mathf.Rad2Deg *    Mathf.Sign(vector2.x));
}
private void MoveLogicMethod()
{
    // Vector2 result = moveInputValue * speed * Time.fixedDeltaTime;
    Vector2 result = moveInputValue.normalized * speed; //fixedDeltaTime is not needed here
    rb2D.velocity = result;

}
    private void FixedUpdate()
    {
        MoveLogicMethod();
    }
//Move End

//Attack Start

public void OnPlayerAttacking()
{
// If attacking too close to previous attack, ignore this input.
if (Time.time - _lastAttackTime < attackCooldown) return;

var enemiesToDamage = Physics2D.OverlapBoxAll(
        attackPos.position,
        new Vector2(attackRangeX, attackRangeY),
        0,
        whatIsEnemies
);

foreach (var enemy in enemiesToDamage) {
     enemy.GetComponent<Enemy>().TakeDamage(damage);
}

_lastAttackTime = Time.time;
 }
 void OnDrawGizmosSelected(){
        
        Gizmos.color = Color.black;
        Gizmos.DrawWireCube(attackPos.position, new Vector2(attackRangeX, attackRangeY));
}
//Attack End
}

I also havent used any if statements yet, since I think it would even further confuse me. Im kind of ashamed asking you for help again, especially after you gave me such a long and good answer.

\$\endgroup\$
2
  • \$\begingroup\$ From looking at this example, I don't see a "facing" state anywhere in the code, nor visible to the player (looks like the sprite always faces forward / toward the screen). Am I missing something, or did you mean to ask how to change the attack range to match the direction in which you're moving, rather than the direction you're facing? \$\endgroup\$
    – DMGregory
    Jan 14 at 13:35
  • \$\begingroup\$ oh yes, sorry, I meant moving. \$\endgroup\$
    – Hammynator
    Jan 14 at 13:43

1 Answer 1

1
\$\begingroup\$

I'm not sure I understand your question 100%, but for the target of attacking left, you can simply treat the Player as a whole and flip it horizontally. In 2D games we often use scale(-1,1) to do this:

public Transform player;
...
private void OnMove(InputValue value)
{
    moveInputValue = value.Get<Vector2>();
    player.localScale = new Vector2((moveInputValue.x > 0?1:-1),1);
}

edit:

After some limited observations, I finally understand what you want. Now let's make a list of what has been achieved and what would like to be achieved.

Achieved:

  1. Player moving.
  2. Damage Dealing.
  3. Attacking cool down.

To be achieved:

  1. The attack direction is determined by the movement direction(360°). This includes the position of the attack point and the rotation of the shape used for detection.

To achieve the player-centered rotation of the attack point, the value that the developer needs to set is no longer a fixed point(attackPos), but a fixed distance(attackRange).

// public Transform attackPos;
public float attackRange; // replace attackPos with attackRange

The calculation of the specific position follows the formula:

attackPos = playerPos + normalized(attackDirection)*attackRange

normalized means keep the direction of the vector and change its length to 1.

vector*number means keep the direction of the vector and multiply its length.

normalized(attackDirection)*attackRange means a vector with a length of attackRange in the attackDirection, this is a relative value, so we need to add the position vector of the player itself.

enter image description here

So now we can do this:

    var attackPos = (Vector2)transform.position + attackDir*attackRange; //Get the world coordinates of attackPos
 
    var enemiesToDamage = Physics2D.OverlapBoxAll(
            attackPos,
            new Vector2(attackRangeX, attackRangeY),
            0,
            whatIsEnemies
    );

*You can use the built-in member variable transform to get the transform of the gameobject where the current script is located.

How can we get attackDir? It essentially depends on the direction of movement, so just use the normalized value of moveInputValue. But when the movement stops, the value of moveInputValue is Vector2.zero, which prevents the attack from executing correctly. When the player stops moving, we need to get the last moving direction as the attack direction. We can set a non-zero initial value for attackDir, and only when moveInputValue is not 0, overwrite attackDir with its normalized value.

private Vector2 moveInputValue;
private Vector2 attackDir = Vector2.right; // independent direction of attack

private void OnMove(InputValue value)
{
    moveInputValue = value.Get<Vector2>();
    if(moveInputValue != Vector2.zero){
        attackDir = moveInputValue.normalized; //When the player stops moving, use the last movement direction as the attack direction
    }
}

Now let's look at rotation. We can use Vector2.Angle and Vector2.SignedAngle to get the angle between two vectors. So we can calculate the angle between a vector and a vector with an angle of 0 to get the angle value of this vector:

var angle = Vector2.SignedAngle(Vector2.up,attackDir); //Cast Vector2 to angle[-180,180], it's used in OverlapBoxAll

Or you can use trigonometric functions to figure out the angle of a vector:

public float Angle(Vector2 vector2)
{
    return 360 - (Mathf.Atan2(vector2.x, vector2.y) * Mathf.Rad2Deg * Mathf.Sign(vector2.x));
}

Finally, we need a visual debugging method. Unity does not have an api for DrawBox with a rotation angle. You can debug it by whatever you like, such as calculating the coordinates of the 4 points of the rectangle and drawing 4 line segments, or using a sprite object as a debugger.

BTW, When you calculate the velocity and apply it to the rigid body, you don't need Time.fixedDeltaTime, This part of the calculation is handled by the physics phase of the engine itself.

private void MoveLogicMethod()
{
    // Vector2 result = moveInputValue * speed * Time.fixedDeltaTime;
    Vector2 result = moveInputValue.normalized * speed; //fixedDeltaTime is not needed here
    rb2D.velocity = result;
}
\$\endgroup\$
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .