4
\$\begingroup\$

I am attempting to make a top down game similar to the SNES Zelda games, in Unity 4.3, in 2D. When my character swings their sword, every enemy within a radius takes damage. I want to make sure that melee attacks only damage enemies the player is facing.

How can I do this?


The relevant code in PlayerAttack.cs looks like this:

// Update is called once per frame
void Update () {
    // have the player initiate an attack on the object
    if(Input.GetKeyUp(KeyCode.F)){
        MeleeAttack();
    }
}

private void MeleeAttack(){
    // create a variable called distance to make sure the distance is close enough
    // to inflict damage from a melee attack target.transform.positon = the position
    // of the object to attack and transform.position is the position of the players
    // transform
    float distance = Vector3.Distance(target.transform.position, transform.position);

    // make sure the distance is not greater than 1.5 so the player cannot
    // melee an enemy from to far away
    if(distance < 1.5f){
            // get a reference to the enemy's health script and type cast it.
            EnemyHealth eh = (EnemyHealth)target.GetComponent("EnemyHealth");
            // now call the function that will decrease the enemy's health
            eh.AdjustCurrentHealth(-10);
    }
}
\$\endgroup\$

5 Answers 5

4
\$\begingroup\$

Compare the direction your character is facing with the direction to the enemy. The dot product of vector A and vector B divided by the magnitude of vector B will tell you the projection of A onto B. If you're using normalized vectors, you can skip the division. The results can be interpreted as follows:

  • Result = 1: Vectors overlap completely
  • Result = 0: Vectors are perpendicular
  • Result = -1: Vectors are exactly opposite

Play with the results, see what ranges you want to allow. I'd suggest a result > .85f.

public float CheckProjection(Vector2 a, Vector2 b) {
    return Vector2.Dot(a.normalized(), b.normalized()); 
}

private void MeleeAttack(){
    ...
    if(CheckProjection(directionToTarget, directionFacing) < .85f) {
        //facing towards the target, do the attack
    }
    ...
}
\$\endgroup\$
2
  • \$\begingroup\$ Can this be applied to a 2d sprite object? Right now my code for moving the player consists of moving the 2d sprite game object up, down, left, and right. I am confused as how to rotate the sprite in such a way that whenever I move that sprite would face that direction. This may be part of my issue. I know how to implement what I am needing using the default 3d character controller, but I cannot seem to implement it with a 2d sprite game object which is new to Unity 4.3. I would much rather use all 2d components. Thanks for your comment! \$\endgroup\$
    – Jeremy
    Commented Jan 31, 2014 at 0:46
  • \$\begingroup\$ Yes, this is applicable to a 2D sprite. This example uses 2D vectors (it's possible in 3D as well). There are a few questions on the site about how to rotating a sprite, these things are language and platform agnostic. Good luck. \$\endgroup\$
    – House
    Commented Jan 31, 2014 at 4:11
2
\$\begingroup\$

Algorithm description: You need the following:

  1. The position (x1, y1) of your character.
  2. The angle alpha in which it is facing.
  3. The positions (and possibly sizes) of all enemy creatures.

Decide if you want the sword slash to be single target or hit all enemies in a cone in the direction you are facing.

General algorithm:

  1. Create a list of all enemies in the vicinity.
  2. Compute distance from each enemy. If not within melee range, remove it.
  3. Iterate over all remaining enemies (within melee range) and compute the angle of the vector of their position minus your position.
  4. Deduct this angle from the angle the character is facing (get absolute value). If the value is closer to 2 * Pie (or 360 degrees) than to zero, deduct 2 * Pie from it and get absolute value again.
  5. Compare the result with what you consider an acceptable cone slice (for instance Pie / 5).
  6. If the enemy is within the range, deduct health.
  7. If you only wanted to hit one target, take the one closest to the character and inside the cone and deduct its health.

Normally you calculate the angle between two characters' positions like this:

Math.atan2(enemy.y - player.y, enemy.x - player.x);

This function or a compatible one probably exists in the language of your preference.

Please specify which part of this algorithm is not clear if you want further assistance.

More details: A melee attack is simply (in game mechanics terms) a ranged attack with a short range. There are generally two kinds of attacks, a single target attack where you only damage one enemy at most and AoE where you damage multiple foes (every character within the range of the area of effect).

In your case, I think you headed in the right direction. Checking the distance. Now you need to check the angle between your character and that enemy and compare it to the angle you are facing. If the difference is too large (PI / 4) then it doesn't hit.

So get the absolute value of the difference between the angles and compare it to some constant.

\$\endgroup\$
4
  • \$\begingroup\$ As you can see by my comments below, I believe that one of the root issues that I am having is related to using a 2d sprite game object. It is either that, or the lack of knowledge of how to connect the solutions you guys gave me to the 2d sprite object. Thanks for the comment! \$\endgroup\$
    – Jeremy
    Commented Jan 31, 2014 at 0:50
  • \$\begingroup\$ @Jeremy It is unrelated. You need to have three things. The position of your character, the angle your character is facing and the positions and sizes of all enemies. The 2d sprite is just for display purposes. The position and and angles the character is facing towards are simply game variables. \$\endgroup\$
    – AturSams
    Commented Jan 31, 2014 at 1:23
  • \$\begingroup\$ I would prefer to be able to kill multiple enemies at once as long as they are in range, but probably no more than would be equal to the width of my character. Pretty much the same way as in the old Zelda games I suppose. How would I calculate the angles of the vector as you have recommended and apply that to my current setup? Thanks again, and I appreciate your help. \$\endgroup\$
    – Jeremy
    Commented Jan 31, 2014 at 2:42
  • \$\begingroup\$ @Jeremy Something like Math.atan2(enemy.y - player.y, enemy.x - player.x); \$\endgroup\$
    – AturSams
    Commented Jan 31, 2014 at 2:47
2
\$\begingroup\$

One solution is clearerer pictorially:

Current situation left, ideal situation right

Left is what's happening; right is what you want.

Clearly, distance from the player is not the only important factor. There's also the enemies' direction.

The direction relative to the player's facing that is:

Directions of enemies relative to the player's facing direction

If you know the position vector of the player p and that of the enemy e, you can observe by a basic trigonometric identity that

tan(angle) = opp / adj, where opp = | p.y - e.y | and adj = | p.x - e.x |.

Hence, angle = atan(abs(p.y - e.y)/abs(p.x - e.x)).

You then filter down the list of enemies, first by whether they're in the radius, then further by whether the angle to them is within the angle of the attack. Everything left gets hit.

\$\endgroup\$
3
  • \$\begingroup\$ I am having trouble wrapping my head around how to get that cone projection to change depending on if I am moving up, down, left, or right. If I move left then that cone has to be to the left, or if I move right then the cone area needs to be to the right. Here is code I have to compute the angle per @Arthur Wulf White suggestion. <!-- language: c# --> float targetAngle = Mathf.Atan2(target.transform.position.y - transform.position.y, target.transform.position.x - transform.position.y); \$\endgroup\$
    – Jeremy
    Commented Feb 2, 2014 at 22:57
  • \$\begingroup\$ @Jeremy You can store the player's previous position and use that same trigonometry as with the direction of enemies to figure out the angle the player last moved in. The left and right boundaries of the arc would then be that angle plus or minus some threshold. (That should work regardless of which way the player is facing.) \$\endgroup\$
    – Anko
    Commented Feb 2, 2014 at 23:01
  • \$\begingroup\$ I will try that and see if I can get it to work. Thanks for your comment! \$\endgroup\$
    – Jeremy
    Commented Feb 2, 2014 at 23:07
1
\$\begingroup\$

There are many ways to achieve this. Here's one way, which should be easy to make work with your existing code.

Create a Transform and make it a child of your player character. Position it where the attack lands and then add it as a public Transform of the PlayerAttack script. Let's call it attackPositionTransform. Now in your MeleeAttack function, you can compare the position of the attackPositionTransform with the target.

float distance = Vector3.Distance(target.transform.position, attackPositionTransform.position);
\$\endgroup\$
1
  • \$\begingroup\$ This makes sense, but how would I get the transform to rotate to accommodate for up, down, left, and right on a 2d sprite object? I think that one of my issues is that I am using a 2d sprite object in Unity 4.3. Thanks for your comment! \$\endgroup\$
    – Jeremy
    Commented Jan 31, 2014 at 0:41
0
\$\begingroup\$

Just make a circle collider to check when an object enters your attack radius, then use foreach to access all the objects near you:

foreach(var item in objects)
    if(item.transform.position - transform.position).sqrMagnitude < 2)
        DamageCandidates.add(item);

Now you have a list with everthing close to you, and you can just use the dot product to check back stabs, and add a effect like stun.


To do the damage, you can just make a box collider, and size it to your needs. This will be the hit area. If the collider intersects with one of all damage candidates, fire your damage method.

OnAttackSend(Damageable victim)
{
    var tmp = new DamageEvent(myDamage, Owner, myEffects, PushMagnitude);
    victim.ApplyDamage(tmp);
}

To update the collider position, just normalize your velocity vector, and the collider position will be that position.

if(velocity != vector2.zero)
    collider.transform.position = parent.position + Velocity.normalized;
\$\endgroup\$

You must log in to answer this question.

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