Limiting number of collisions from OnTriggerEnter/Exit event with melee weapon.

I know this is asked quite a bit and I saw half a dozen threads about this when I was trying to get my collisions to register that I wasn’t able to find when looking for them specifically.

After more hours than I’ll admit troubleshooting EVERYTHING UNDER THE SUN I found out I was passing OnTriggerExit(Collision collision) INSTEAD of OnTriggerExit(Collider collision) :hushed: but the moral of that story is I answered it myself while proof reading my forum post on that issue.

But now the issue is my double swing animation registers 5+ hits each time it swings at a monster as it goes through the monster.

I have 2 scripts, Shooter.cs and Shootable.cs

Shooter.cs:

void Update ( )
    {
        // 1. Wait for a mouse click.
        if ( Input.GetButtonDown( "Fire1" ) )
        {
		meleeSwing();
}

         if(Input.GetButtonUp("Fire1")) {
		Shoot( );
}

Shoot() {
--code from ray cast--
}
meleeSwing() {
SendMessageUpwards("doubleSwording");
Debug.Log("Melee Swing");		
}

void OnTriggerExit(Collider collision) {
         Debug.Log("Collision Entered"); 
         collision.transform.SendMessage("OnBullet",
                SendMessageOptions.DontRequireReceiver);
}

Inside Shootable.cs is the following:

void OnBullet ( )
    {
	Debug.Log("on bullet");
        Damage( );	
    }
 void Damage ( )
    {     
        health = health - 1;       
        if ( health == 0 )
        {
	    Kill( );
        }
void Kill ( )
    {		
		SendMessageUpwards("dead");		
		Instantiate(prefab, transform.position, Quaternion.identity);
		Destroy( gameObject );
    }

Combined with the 2 debug statements I get like twice as many “Collision entered” messages as “on bullet”. I wonder if that’s from hitting my own character?

Combined from a single swing there’s 4 “on bullet” and about 8 “Collision entered” messages…if I even brush up against a mob it gets hit so many times it dies before I even start swinging.

I tried to use a bool and timer variable to control when Damage() would fire inside OnBullet() to no avail.

Thanks in advance.

Also is there any way to create a force inside an object so it doesn’t just cut through like thin air? Or to check velocity of the weapon impact before applying damage so it has to be swinging and not just brushed against slightly when turning?

EDIT:::::::

I got a bool variable working but I can’t figure out how to yield, it gives me errors anywhere I put yield WaitForSeconds(3); even inside IEnumerator functions. I dunno.

I honestly can’t tell what your are trying to do from the code you pasted. I can only say, based on your question, that you should try letting the sword do the processing. If it enters an enemy, then do whatever you need to. If the hit is good, then SendMessage(“OnHit”) or something to the enemy. The sword should know if it is in a swing or idle (or at least something in the character should).

For example, in psuedo code: OnEnemyHit, if isSwinging, SendMessage(“OnHit”, damageToApply)

You can also limit the hits per collider to one per swing this way quite easily. It just makes sense to have attack logic on the attacker IMO. The enemy can be ignorant until told it is hit, and can then respond however you need.

That helps a lot, now the only problem is when I hold the key down it repeatedly hits until the monster dies. But outside of that they no longer take damage when I brush against them unless I hit the fire key.

I added:
private bool isSwinging = false;

then inside the update:

 if ( Input.GetButtonDown( "Fire1" )) {
			isSwinging = true;
			meleeSwing();
        }

  if(Input.GetButtonUp("Fire1")) {
		isSwinging = false;
        }

and outside of the update after Shoot() and meleeSwing():

void OnTriggerExit(Collider collision) {
		[B]if(isSwinging == true)[/B] {
	Debug.Log("Collision Entered"); 
collision.transform.SendMessage("OnBullet",
                SendMessageOptions.DontRequireReceiver);
     }		
}

But the problem now is if you hold the Fire1 button and don’t let up it calculates new damage each frame.

EDIT:::

Got it working wOOt!

   if ( Input.GetButtonDown( "Fire1" ))
        {
			isSwinging = true;
			meleeSwing();
		}	
			if(Input.GetButtonUp("Fire1")) {
				isSwinging = false;
		//			   Shoot( );
		}


void OnTriggerExit(Collider collision) {
		if(isSwinging == true) {
	Debug.Log("Collision Entered"); 
collision.transform.SendMessage("OnBullet",
                SendMessageOptions.DontRequireReceiver);
			isSwinging = false;
		}
		
}

Instead of using Update(), can you start a coroutine when you press the fire button? That way you don’t have the overhead of constantly processing and you can be sure you get one swing per button press.

Then you’ll have to make sure you don’t allow another swing until the first one has completed. Your isSwinging switch should just bypass any swing attempts until the first swing is done. If you determine a swing should last 1 second, then yield new WaitForSeconds(1) before you set isSwinging back to false (in the coroutine). This mechanism should prevent more than one co-routine from launching because it prevents a second swing until the coroutine is done, and the coroutine IS the swing… does that make sense? lol.

I wish I could get coroutines to work. I’ve read through tutorials on them and haven’t figured out how to implement it with the rest of my stuff…

Problem is I use a state machine for movement and another script for input, and don’t have a clue where to put the yield where it won’t throw an error. I’ve taken yield coroutines from tutorials said to be working and it just gives errors everywhere I try.

EDIT::

I would love coroutines for spells to allow particle effects/spell animations to go…so far I’m using a bunch of if else statements…

Here’s a sample from AdvancedMovement.cs

IEnumerator Start () {
	myAnimation=GetComponent<Animation>();
    myAnimation.AddClip(fullAnimation,"arch_knock",1,35,false);
    myAnimation.AddClip(fullAnimation,"arch_hold",35,36,true);
    myAnimation.AddClip(fullAnimation,"arch_release",36,55,false);
	
	myAnimation2=GetComponent<Animation>();
	myAnimation2.AddClip(fullAnimationBlock,"block",1,9,false);
    myAnimation2.AddClip(fullAnimationBlock,"block_hold",9,10,true);	
	myAnimation2.AddClip(fullAnimationBlock,"unblock",10,20,false);
		
		while(true) {
			switch(_state) {
			case State.Init:
				Init();
				break;
			case State.Setup:
				SetUp();
				break;
			case State.Run:
				ActionPicker();		
				break;
			}
			yield return null;
		}		
}


private void ActionPicker() {
		_myTransform.Rotate(0, (int)_turn * Time.deltaTime * rotateSpeed, 0);
	//_myTransform.Rotate(0,Input.GetAxisRaw("Mouse X") * Time.deltaTime * rotateSpeed , 0);
	if(_controller.isGrounded || _isSwimming || _isCrouching) {
		airTime = 0;
		_moveDirection = new Vector3((int)_strafe, 0, (int)_forward);
		_moveDirection = _myTransform.TransformDirection(_moveDirection).normalized;
		_moveDirection *= walkSpeed;
		
		if(_forward != Forward.none) {
			if(_isSwimming) {
				Swim();
			}
			else if(_isCrouching) {
				CrouchWalk();
			}			
			else {
			if(_run) {
				_moveDirection *= runMultiplier;
				Run();
			}			
			else {
			Walk();		
			}	
		}
	}
	else if(_strafe != AdvancedMovement.Turn.none) {
		Strafe();
		}
		else {
			if(_isSwimming || _isCrouching) {
		}
		else {
			Idle();
			}
		}		

	if(_jump) {
			if(airTime < jumpTime) {
				_moveDirection.y += jumpHeight;			
						//animation.CrossFade("jump complete");
					Jump();
					_jump = false;				
			}
		} 
	else if(_block) {
			BlockStart();
			_block = false;		
	}
	else if(_blockHold) {
		BlockFreeze();
		//_blockHold = false;
	}
	else if (_blockRelease) {
		_blockHold = false;
		BlockFinish();
		_blockRelease = false;
	}
	else if(_axe) {
	//	Debug.Log("Axe Attacking");
		AxeAttack();
		_axe = false;
	}
	else if (_sword) {
		SwordAttack();
		_sword = false;
	}
	else if (_doubleSword) {
		doubleSwordAttack();
		_doubleSword = false;
	}
	else if (_archerBegin) {
		ArcherStart();
		_archerBegin = false;
	}
	else if (_archerHold) {
		ArcherFreeze();
	//	_archerHold = false;
	}
	else if (_archerRelease) {
	//	Debug.Log("Release");
	_archerHold = false;
	ArcherFire();
	_archerRelease = false;
	}	
	else if (_camToggle) {		
//		Debug.Log("Toggle = true;");
		SwitchCameras();
		_camToggle = false;
	}
	} 
		else {
		//	Debug.Log("Not on Ground");		
		if((_collisionFlags  CollisionFlags.CollidedBelow) == 0) {
			airTime += Time.deltaTime;			
			if(airTime > fallTime) {		
				_jump = false;					
				Fall();
			}
		}
	}
	if(!_isSwimming)
	_moveDirection.y -= gravity * Time.deltaTime;
	_collisionFlags = _controller.Move(_moveDirection * Time.deltaTime);	
	}

I know there must be a more efficient way to have all those various actions in an array or something I dunno, it’s really ugly lol…

Imbarns; I think what you wrote works with one enemy only. As soon as you hit something, the isSwinging will go false and thus not damage any other enemies you may hit with the same swing. If you’re hitting just one enemy at time it’ll be perfect, but if you want to hit multiple enemies once per swing, you’d need something else :slight_smile:

I implemented a melee system in UDK where the melee weapon would process hits every frame, but when an enemy was hit, it would be added to a list of enemies who have already been hit during that swing. Then in the next frames of the swing even if the enemy was hit again, it would be ignored. After the swing the list of enemies already hit would be cleared, so that they could be hit again in the next swing.

Now I’m moving from UDK to Unity and looking for other ways to implement melee combat, thus browsing through these topics. If nothing else pops up, I’ll use the same method I used in UDK. (which is not my idea btw, but from Dungeon Defense http://www.udk.com/showcase-dungeon-defense).

Hope this helps!