RPG Swing attack hit detection

Heya,

I am making a small RPG, has currently a player that swings a sword around. I got some enemys running around that I want to hit.
My problem is detecting when the sword hit the enemies.
So far I have tested two ways of doing it:

  1. Calculating where the enemies are compared to myself (using distance and angle to enemies)
  2. When the enemies collid with the sword.

The 1. solution I didn’t like because it feels like alot of calculations. I have maybe up to 150 enemies on the screen at a time, it didn’t lag on my comp but i can imagine it could be a problem on some machines.

The 2. solution in theory felt like a nice one, having the physics engine to take care of it. Problem is, it doesn’t detect all the hits. Alot of the times the sword is passing through the enemy, but doesn’t trigger an collision. It is rather random sometimes it triggers alot of collisions, other times non. I have tried debug stepping through an sword attack, and even through the sword is right in(colliding with) an enemy, it doesn’t trigger an collision.
The animation of the sword is rather fast, which could be an explanation (the sword is on one side of the enemy in one frame, and on the other side of the enemy in the next). The animation takes about 0.3 seconds. Making it take longer is not an option.
The sword is the one getting triggered, and tells my swingAttackScript it has hit an enemy.

My code on the sword that gets called when there is a collision is:

var swingAttack : SwingAttacksScript;

private var enable : boolean = false;
private var enemiesHitThisSeason : ArrayList;


function SetEnable(setEnable : boolean)
{
	enemiesHitThisSeason = new ArrayList();
	enable = setEnable;
}
function OnTriggerEnter(other:Collider)
{
	OnTrig(other);
}

function OnTriggerStay(other:Collider)
{
	OnTrig(other);
}

function OnTriggerExit(other:Collider)
{
	OnTrig(other);
}

function OnTrig(other:Collider)
{
	if(enable)
	{
		var enemy : BaseEnemy = FindBaseEnemy(other.transform);
		if(enemy!=null && !enemiesHitThisSeason.Contains(enemy))
		{
			enemiesHitThisSeason.Add(enemy);
			swingAttack.EnemyHitWithMainWeapon(enemy);
		}
	}
}

function FindBaseEnemy(from : Transform)
{
	var parent= from;
	while(parent!=null){
		var enemy =parent.GetComponent(BaseEnemy);
		if(enemy !=null)
		{
			return enemy;
		}
		else
		{
			parent=parent.parent;
		}
	}
	
	return null;
}

I keep an array that knows all the enemies, so I can always get an array with all my enemies (Takes O(1) time (constant time)).

So ye, my question is, how do i make a good “sword hit detection”?

If all you’re worried about for angle+distance is checking against every enemy, Physics.OverlapSphere(transform.position, weaponRange, ~(1<<enemyLayer)); should get you a quick list of potentials. The note about getting only AABB’s means it will grab a few more than you need, but not miss any.

For #2, you have to worry about always missing dogs (swing over their heads,) thrusts through armpits and between legs, halberds with a downswing and left follow-through that never hit to the right… . Are you making King`s Field or Monster Hunter, where knowing the weapon arcs is part of fun (I always took a downswinging weapon to fight short enemies, since I hate tilting the camera down.)

My nephew has me playing the DungeonCrawler Fate – that style of game, no one cares if the weapon animations “hit.” You can barely see them. In an MMO-style, no one complains “that baseball bat went completely through my torso. How is that possible, and why am I not cut in half?”

I have been making a multiplayer brawler, and I go for a variant of solution 2. What I do is for every weapon, have a set of empty transforms which define a simplified ‘edge’ for the weapon, and then while it is being swung, I remember the difference between the positions of each empty and their positions last frame, and use Physics.Linecast to find out if anything got hit in between. This provides a very accurate measure of where the weapon is going, because it doesn’t rely on an object not ‘phasing’ through because of the animation. The only thing to check here, is to make sure that the weapon doesn’t hit something twice in the same swing- maybe when it hits an enemy, make the enemy stop receiving damage for half a second or so.

I think solution 1 is your best bet. You are correct in assuming that the sword is moving too fast and the physics engine is missing the collision. I would put a cylinder collider around your player and make it a trigger, when an enemy is in the trigger, play the swing animation and take damage from the enemy.

You could decrease the Fixed Timestep value under Project Settings → Time. Although depending on the number of rigidbodies in the scene, this might make it laggy.

Quick trick to collision compatability is to set the game at a fixed framerate of about 10fps. Animations are based per update on time passed since last. If you only check 10 frames a second, you need to find a way to catch between swings. Usually, this emplies creating multiple collision checks in frames it has gone through, and not just the current frame. this is done with bullet travel using raycasts to where it is assumed to have passed through since last frame. Using a sphere-cast might be a bit much, but is a good anti cheat measure. You could create a collider mesh (or check) for the full swing to determine if it will hit, and set delays accordingly. The other guys collider node is a good eidea, as it pretends like its shooting multiple bullets that follow the animation. Just save the position in a buffer for each n

  • List item

ext frame.

This is sorta old but i wanted to give an answer for future viewers, i had the same problem. Weapon was moving too fast for the collider to detect the swing. Made empty children on my weapon to hold transforms. Then used this code to detect when attacking and draw rays between the current and last update position of the transforms on the weapon. So pretty much, the code to what syclamoth answer suggested to do

void LateUpdate()
{
    if (GetComponent<Melee>().attacking)
    {
        var weapon = Player.instance.GetComponent<Player>().Weapon_Slot.transform.GetChild(0);
        for (var i = 0; i < weapon.childCount; i++)
        {
            if (spots0 *== Vector3.zero)*

{
spots0 = weapon.GetChild(i).transform.position;
}
else
{
spots1 = spots0*;*
spots0 = weapon.GetChild(i).transform.position;
}
// Debug.Log(spots0 + “:” + spots1*);*
if (spots0 != Vector3.zero && spots1 != Vector3.zero)
{
RaycastHit hit;
float ent = 1;
int mask = LayerMask.GetMask(“ColliderLayer”, “ColliderInactiveLayer”);
if (Physics.Raycast(spots0, spots1 - spots0*, out hit, ent, mask))*
{
if (hit.collider.tag == “Enemy”)
{
BodyColliderScript bcs = hit.collider.GetComponent();
int[] parts = new int[] { bcs.index };
bcs.ParentRagdollManager.StartHitReaction(parts, Vector3.up * 16.0f);
}
Debug.DrawRay(spots0, spots1 - spots0 * ent, Color.green);
}
}
}
}
}