killed unity by a HeadShot (?)

hello everybody…
Today, something strange happedned: :smile:
I wrote a headshot-code for the AI.js-script and when I started the game, everything went normally, but when I killed the charcter, unity crashed… :shock:

That’s the code for the headshots:

var hitSound : AudioClip;
var DMGheight1 = 12.0;
var apply : boolean = true;
var Character : AI;
private var collisionMagnitude : float;
var HeadShots : float;

killed = false;

function OnCollisionEnter (hit : Collision) {
	if (apply == true) {
		if (hit.relativeVelocity.magnitude >= DMGheight1) {
			Character.gameObject.animation.CrossFade("hit");
			collisionMagnitude = hit.relativeVelocity.magnitude;

		}
		if (hit) {
			HeadShots++;
			audio.PlayOneShot(hitSound, 0.5);
		}
	}
}

function ApplyDamage (damage : float) {
	// We already have less than 0 hitpoints, maybe we got killed already?
	if (Character != null) {
		Character.SendMessage("ApplyDamage", collisionMagnitude * 2);
	}
	if (Character == null) {
		Destroy(GetComponent(AiHeadShot1));
	}
}

function Destroy (killed : boolean) {
	if (killed == true) {
		Destroy(GetComponent(AiHeadShot1));
	}
	else {
	}
}

And this is my (modified) AI-code:

var speed = 3.0;
var rotationSpeed = 5.0;
var shootRange = 15.0;
var attackRange = 30.0;
var shootAngle = 4.0;
var dontComeCloserRange = 5.0;
var delayShootTime = 0.35;
var pickNextWaypointDistance = 2.0;
var target : Transform;
var hitSound : AudioClip;
var hitPoints = 100;
var dieSound : AudioClip;
var playSound : boolean = true;
var playAnim : boolean = true;
var jumpSpeed = 8.0;
var JumpSound1 : AudioClip;
var JumpChecker : AiJumpWallChecker1;
private var jump : boolean = false;
var headShotDanger : boolean = false;
private var lastShot = -10.0;

var DMGheight1 = 12.0;
var apply : boolean = true;
var Head : AiHeadShot1;

private var moveDirection = Vector3.zero;

SetRagdollEnabled(false);

function OnCollisionEnter (hit : Collision) {
	if (apply == true) {
		if (hit.relativeVelocity.magnitude >= DMGheight1) {
			animation.CrossFade("hit");
			hitPoints -= hit.relativeVelocity.magnitude/2;
			if (hitPoints <= 0.0) {
				SetRagdollEnabled(true);
			}

		}
	}
}

function ApplyDamage (damage : float) {
	// We already have less than 0 hitpoints, maybe we got killed already?
	if (hitPoints <= 0.0)
		return;

	hitPoints -= damage;
	if (hitPoints <= 0.0) {
		SetRagdollEnabled(true);
	}
}
	
function SetRagdollEnabled (enableRagdoll : boolean) {
	animation.Stop();
	var bodies : Component[] = GetComponentsInChildren(Rigidbody);
	for (var body : Rigidbody in bodies)
	{
		if (enableRagdoll) {
			body.detectCollisions = true;
			body.isKinematic = false;
			if (playSound == true) {
				audio.PlayOneShot(dieSound);
				playSound = false;
				Destroy(GetComponent(AI));
				Destroy(GetComponent(CharacterController));
			}
			hitPoints = 0.0;
			Head.SendMessage("Destroy", true);
		}
		else {
			Head.rigidbody.detectCollisions = true;		
			Head.rigidbody.isKinematic = true;		
			body.detectCollisions = false;
			body.isKinematic = true;
		}
	}
}

// Make sure there is always a character controller
@script RequireComponent (CharacterController)

function Start () {
	// Auto setup player as target through tags
	if (target == null  GameObject.FindWithTag("Player"))
		target = GameObject.FindWithTag("Player").transform;
	if (target == null) {
		GameObject.FindWithTag("Player");
	}

	Patrol();
}

function Patrol ()
{
	var curWayPoint = AutoWayPoint.FindClosest(gameObject.transform.position);
	while (true)	
	{
		var waypointPosition = curWayPoint.gameObject.transform.position;
		// Are we close to a waypoint? -> pick the next one!
		if (Vector3.Distance(waypointPosition, transform.position) < pickNextWaypointDistance)
			curWayPoint = PickNextWaypoint (curWayPoint);

		// Attack the player and wait until
		// - player is killed
		// - player is out of sight		
		if (CanSeeTarget ())
			yield StartCoroutine("AttackPlayer");
		
		// Move towards our target
		MoveTowards(waypointPosition);
		
		yield;
	}
}

function Jump () {
	audio.PlayOneShot(JumpSound1, 0.5);
	animation.CrossFade("jump");
	yield new WaitForSeconds (0.4);
	jump = true;
	yield new WaitForSeconds (0.2);
	jump = false;
	JumpChecker.SendMessage("ResetJumper", true);
}
function MissileDanger () {
	animation.CrossFade("jump");
	yield new WaitForSeconds (0.3);
	headShotDanger = true;
	yield new WaitForSeconds (0.2);
	headShotDanger = false;	
	yield;
}

function Update () {
	if (jump == true) {
		transform.Translate(0, jumpSpeed * 1.2 * Time.deltaTime, jumpSpeed * Time.deltaTime, Space.World);
	}
	if (headShotDanger == true) {
		transform.Translate(jumpSpeed * Time.deltaTime, jumpSpeed * Time.deltaTime, 0, Space.World);
	}
}

function CanSeeTarget () : boolean
{
	if (Vector3.Distance(transform.position, target.position) > attackRange)
		return false;
		
	var hit : RaycastHit;
	if (Physics.Linecast (transform.position, target.position, hit))
		return hit.transform == target;

	return false;
}

function Shoot ()
{
	// Start shoot animation
	animation.CrossFade("shoot", 0.3);

	// Wait until half the animation has played
	yield WaitForSeconds(delayShootTime);
	
	// Fire gun
	BroadcastMessage("Fire");
	
	// Wait for the rest of the animation to finish
	yield WaitForSeconds(animation["shoot"].length - delayShootTime);
}

function AttackPlayer ()
{
	var lastVisiblePlayerPosition = target.position;
	while (true)
	{
		if (CanSeeTarget ())
		{
			// Target is dead - stop hunting
			if (target == null)
				return;

			// Target is too far away - give up	
			var distance = Vector3.Distance(transform.position, target.position);
			if (distance > shootRange * 3)
				return;
			
			lastVisiblePlayerPosition = target.position;
			if (distance > dontComeCloserRange)
				MoveTowards (lastVisiblePlayerPosition);
			else
				RotateTowards(lastVisiblePlayerPosition);

			var forward = transform.TransformDirection(Vector3.forward);
			var targetDirection = lastVisiblePlayerPosition - transform.position;
			targetDirection.y = 0;

			var angle = Vector3.Angle(targetDirection, forward);

			// Start shooting if close and play is in sight
			if (distance < shootRange  angle < shootAngle)
				yield StartCoroutine("Shoot");
		}
		else
		{
			yield StartCoroutine("SearchPlayer", lastVisiblePlayerPosition);
			// Player not visible anymore - stop attacking
			if (!CanSeeTarget ())
				return;
		}

		yield;
	}
}

function SearchPlayer (position : Vector3)
{
	// Run towards the player but after 3 seconds timeout and go back to Patroling
	var timeout = 3.0;
	while (timeout > 0.0)
	{
		MoveTowards(position);

		// We found the player
		if (CanSeeTarget ())
			return;

		timeout -= Time.deltaTime;
		yield;
	}
}

function RotateTowards (position : Vector3)
{
	SendMessage("SetSpeed", 0.0);
	
	var direction = position - transform.position;
	direction.y = 0;
	if (direction.magnitude < 0.1)
		return;
	
	// Rotate towards the target
	transform.rotation = Quaternion.Slerp (transform.rotation, Quaternion.LookRotation(direction), rotationSpeed * Time.deltaTime);
	transform.eulerAngles = Vector3(0, transform.eulerAngles.y, 0);
}

function MoveTowards (position : Vector3)
{
	var direction = position - transform.position;
	direction.y = 0;
	if (direction.magnitude < 0.5)
	{
		SendMessage("SetSpeed", 0.0);
		return;
	}
	
	// Rotate towards the target
	transform.rotation = Quaternion.Slerp (transform.rotation, Quaternion.LookRotation(direction), rotationSpeed * Time.deltaTime);
	transform.eulerAngles = Vector3(0, transform.eulerAngles.y, 0);

	// Modify speed so we slow down when we are not facing the target
	var forward = transform.TransformDirection(Vector3.forward);
	var speedModifier = Vector3.Dot(forward, direction.normalized);
	speedModifier = Mathf.Clamp01(speedModifier);

	// Move the character
	direction = forward * speed * speedModifier;
	GetComponent (CharacterController).SimpleMove(direction);
	
	SendMessage("SetSpeed", speed * speedModifier, SendMessageOptions.DontRequireReceiver);
}

function PickNextWaypoint (currentWaypoint : AutoWayPoint)
{
	// We want to find the waypoint where the character has to turn the least

	// The direction in which we are walking
	var forward = transform.TransformDirection(Vector3.forward);

	// The closer two vectors, the larger the dot product will be.
	var best = currentWaypoint;
	var bestDot = -10.0;
	for (var cur : AutoWayPoint in currentWaypoint.connected)
	{
		var direction = Vector3.Normalize(cur.transform.position - transform.position);
		var dot = Vector3.Dot(direction, forward);
		if (dot > bestDot  cur != currentWaypoint)
		{
			bestDot = dot;
			best = cur;
		}
	}
	
	return best;
}

So I ask:
What’s the problem?

Start by adding Debug.Log messages throughout your script in order to determine which part of the code is causing the crash.

When unity crashes, go to Console.app and open up the editor log (LOG FILES → ~/Library/Logs → Unity → Editor.log) to see which of your Debug.Log messages are displayed just before the editor crashes.

I found the problem:

function Destroy (killed : boolean) {
	if (killed == true) {
		Destroy(GetComponent(AiHeadShot1));
	}
}

I don’t know why, but when I delete this part, the code is working…

Possibly GetComponent(AiHeadShot1) returns null. It doesn’t seem unlikely that Destroy( null ); would make stuff break.

Whenever using GetComponent, you almost always want to test against it (FYI: the following code hasn’t been tested).

function Destroy (killed : boolean) {
     if (killed) {
          var headShot : AIHeadShot1 = GetComponent(AiHeadShot);
          if (headShot) {
               Destroy(headShot);
          }
     }
}

In practice, I usually use GetComponentsInChildren (especially if it’s something to do with the Renderer or an emitter which might be attached to multiple children) and run through the resulting array rather than do this test since GetComponentsInChildren is guaranteed to at least return an empty array rather than a potentially null Component.

As a side note to this conversation, you don’t have to test a boolean against the true value; if its value is true, it will fulfill the test statement on its own. Likewise, Unity types can be tested against null in the same way. If an object is non-null, it will satisfy the test condition on its own.

Don’t you have to put the component in quotes if it’s a script? I know it always complains to me about that when I do it… Of course, I always do it by saying gameObject.GetComponent(“AiHeadShot1”);

But yeah… You should always check to see if you’ve got something before you try to destroy it. Weird things happen when you destroy empty values.

I think as long as you’re going from one JS-style script to another, they’re treated as recognized Unity types. I haven’t had any problems, at least, with things like:

function Awake () {
     //...
     skillUseScript = GetComponent(UseSkills);
}

Where UseSkills.js is a script. Where you might run into trouble is if you use a space or some other non-alphanumeric character in your scripts’ names.

Using the type directly rather than using a string is better in most cases.

For one, it’s faster because the compiler can optimize and there’s no string that has to be parsed on runtime.
Likewise, if the type cannot be found because it got deleted/renamed or whatever, you’ll get an error telling you there’s something wrong when compiling. When using a string, you’ll only get an error when running the game and the corresponding code actually gets called (which it might not if you don’t test throughly).

The only case where using a string makes sense is if you’ve got a compile-order conflict, that cannot be resolved otherwise (like a Boo script using GetComponent to get a JS script that’s in the same compile step).

Thank you for help guys,
burnumd’s method works! :slight_smile:
These are the working codes for those who could need them:
AI.js :

var speed = 3.0;
var rotationSpeed = 5.0;
var shootRange = 15.0;
var attackRange = 30.0;
var shootAngle = 4.0;
var dontComeCloserRange = 5.0;
var delayShootTime = 0.35;
var pickNextWaypointDistance = 2.0;
var target : Transform;
var hitSound : AudioClip;
var hitPoints = 100;
var dieSound : AudioClip;
var playSound : boolean = true;
var playAnim : boolean = true;
var jumpSpeed = 8.0;
var JumpSound1 : AudioClip;
var JumpChecker : AiJumpWallChecker1;
private var jump : boolean = false;
var headShotDanger : boolean = false;
private var lastShot = -10.0;

var DMGheight1 = 12.0;
var apply : boolean = true;
var Head : AiHeadShot1;

private var moveDirection = Vector3.zero;

SetRagdollEnabled(false);

function OnCollisionEnter (hit : Collision) {
	if (apply == true) {
		if (hit.relativeVelocity.magnitude >= DMGheight1) {
			animation.CrossFade("hit");
			hitPoints -= hit.relativeVelocity.magnitude/2;
			if (hitPoints <= 0.0) {
				SetRagdollEnabled(true);
			}

		}
	}
}

function ApplyDamage (damage : float) {
	// We already have less than 0 hitpoints, maybe we got killed already?
	if (hitPoints <= 0.0)
		return;

	hitPoints -= damage;
	if (hitPoints <= 0.0) {
		SetRagdollEnabled(true);
	}
}
	
function SetRagdollEnabled (enableRagdoll : boolean) {
	animation.Stop();
	var bodies : Component[] = GetComponentsInChildren(Rigidbody);
	for (var body : Rigidbody in bodies)
	{
		if (enableRagdoll) {
			body.detectCollisions = true;
			body.isKinematic = false;
			if (playSound == true) {
				audio.PlayOneShot(dieSound);
				playSound = false;
				Destroy(GetComponent(AI));
				Destroy(GetComponent(CharacterController));
				Head.SendMessage("Kill", true);
			}
			hitPoints = 0.0;
		}
		else {
			Head.rigidbody.detectCollisions = true;		
			Head.rigidbody.isKinematic = true;		
			body.detectCollisions = false;
			body.isKinematic = true;
		}
	}
}

// Make sure there is always a character controller
@script RequireComponent (CharacterController)

function Start () {
	// Auto setup player as target through tags
	if (target == null  GameObject.FindWithTag("Player"))
		target = GameObject.FindWithTag("Player").transform;
	if (target == null) {
		GameObject.FindWithTag("Player");
	}

	Patrol();
}

function Patrol ()
{
	var curWayPoint = AutoWayPoint.FindClosest(gameObject.transform.position);
	while (true)	
	{
		var waypointPosition = curWayPoint.gameObject.transform.position;
		// Are we close to a waypoint? -> pick the next one!
		if (Vector3.Distance(waypointPosition, transform.position) < pickNextWaypointDistance)
			curWayPoint = PickNextWaypoint (curWayPoint);

		// Attack the player and wait until
		// - player is killed
		// - player is out of sight		
		if (CanSeeTarget ())
			yield StartCoroutine("AttackPlayer");
		
		// Move towards our target
		MoveTowards(waypointPosition);
		
		yield;
	}
}

function Jump () {
	audio.PlayOneShot(JumpSound1, 0.5);
	animation.CrossFade("jump");
	yield new WaitForSeconds (0.4);
	jump = true;
	yield new WaitForSeconds (0.2);
	jump = false;
	JumpChecker.SendMessage("ResetJumper", true);
}
function MissileDanger () {
	animation.CrossFade("jump");
	yield new WaitForSeconds (0.3);
	headShotDanger = true;
	yield new WaitForSeconds (0.2);
	headShotDanger = false;	
	yield;
}

function Update () {
	if (jump == true) {
		transform.Translate(0, jumpSpeed * 1.2 * Time.deltaTime, jumpSpeed * Time.deltaTime, Space.World);
	}
	if (headShotDanger == true) {
		transform.Translate(jumpSpeed * Time.deltaTime, jumpSpeed * Time.deltaTime, 0, Space.World);
	}
}

function CanSeeTarget () : boolean
{
	if (Vector3.Distance(transform.position, target.position) > attackRange)
		return false;
		
	var hit : RaycastHit;
	if (Physics.Linecast (transform.position, target.position, hit))
		return hit.transform == target;

	return false;
}

function Shoot ()
{
	// Start shoot animation
	animation.CrossFade("shoot", 0.3);

	// Wait until half the animation has played
	yield WaitForSeconds(delayShootTime);
	
	// Fire gun
	BroadcastMessage("Fire");
	
	// Wait for the rest of the animation to finish
	yield WaitForSeconds(animation["shoot"].length - delayShootTime);
}

function AttackPlayer ()
{
	var lastVisiblePlayerPosition = target.position;
	while (true)
	{
		if (CanSeeTarget ())
		{
			// Target is dead - stop hunting
			if (target == null)
				return;

			// Target is too far away - give up	
			var distance = Vector3.Distance(transform.position, target.position);
			if (distance > shootRange * 3)
				return;
			
			lastVisiblePlayerPosition = target.position;
			if (distance > dontComeCloserRange)
				MoveTowards (lastVisiblePlayerPosition);
			else
				RotateTowards(lastVisiblePlayerPosition);

			var forward = transform.TransformDirection(Vector3.forward);
			var targetDirection = lastVisiblePlayerPosition - transform.position;
			targetDirection.y = 0;

			var angle = Vector3.Angle(targetDirection, forward);

			// Start shooting if close and play is in sight
			if (distance < shootRange  angle < shootAngle)
				yield StartCoroutine("Shoot");
		}
		else
		{
			yield StartCoroutine("SearchPlayer", lastVisiblePlayerPosition);
			// Player not visible anymore - stop attacking
			if (!CanSeeTarget ())
				return;
		}

		yield;
	}
}

function SearchPlayer (position : Vector3)
{
	// Run towards the player but after 3 seconds timeout and go back to Patroling
	var timeout = 3.0;
	while (timeout > 0.0)
	{
		MoveTowards(position);

		// We found the player
		if (CanSeeTarget ())
			return;

		timeout -= Time.deltaTime;
		yield;
	}
}

function RotateTowards (position : Vector3)
{
	SendMessage("SetSpeed", 0.0);
	
	var direction = position - transform.position;
	direction.y = 0;
	if (direction.magnitude < 0.1)
		return;
	
	// Rotate towards the target
	transform.rotation = Quaternion.Slerp (transform.rotation, Quaternion.LookRotation(direction), rotationSpeed * Time.deltaTime);
	transform.eulerAngles = Vector3(0, transform.eulerAngles.y, 0);
}

function MoveTowards (position : Vector3)
{
	var direction = position - transform.position;
	direction.y = 0;
	if (direction.magnitude < 0.5)
	{
		SendMessage("SetSpeed", 0.0);
		return;
	}
	
	// Rotate towards the target
	transform.rotation = Quaternion.Slerp (transform.rotation, Quaternion.LookRotation(direction), rotationSpeed * Time.deltaTime);
	transform.eulerAngles = Vector3(0, transform.eulerAngles.y, 0);

	// Modify speed so we slow down when we are not facing the target
	var forward = transform.TransformDirection(Vector3.forward);
	var speedModifier = Vector3.Dot(forward, direction.normalized);
	speedModifier = Mathf.Clamp01(speedModifier);

	// Move the character
	direction = forward * speed * speedModifier;
	GetComponent (CharacterController).SimpleMove(direction);
	
	SendMessage("SetSpeed", speed * speedModifier, SendMessageOptions.DontRequireReceiver);
}

function PickNextWaypoint (currentWaypoint : AutoWayPoint)
{
	// We want to find the waypoint where the character has to turn the least

	// The direction in which we are walking
	var forward = transform.TransformDirection(Vector3.forward);

	// The closer two vectors, the larger the dot product will be.
	var best = currentWaypoint;
	var bestDot = -10.0;
	for (var cur : AutoWayPoint in currentWaypoint.connected)
	{
		var direction = Vector3.Normalize(cur.transform.position - transform.position);
		var dot = Vector3.Dot(direction, forward);
		if (dot > bestDot  cur != currentWaypoint)
		{
			bestDot = dot;
			best = cur;
		}
	}
	
	return best;
}

AiHeadShot1.js :

var hitSound : AudioClip;
var DMGheight1 = 12.0;
var apply : boolean = true;
var Character : AI;
private var collisionMagnitude : float;
var HeadShots : float;

killed = false;

function OnCollisionEnter (hit : Collision) {
	if (apply == true) {
		if (hit.relativeVelocity.magnitude >= DMGheight1) {
			Character.gameObject.animation.CrossFade("hit");
			collisionMagnitude = hit.relativeVelocity.magnitude;

		}
		if (hit) {
			HeadShots++;
			audio.PlayOneShot(hitSound, 0.5);
		}
		if (Character != null) {
			Character.SendMessage("ApplyDamage", collisionMagnitude * 2);
		}

	}
}

function ApplyDamage (damage : float) {
	if (apply == true) {
		// We already have less than 0 hitpoints, maybe we got killed already?
		if (damage) {
			if (Character != null) {
				Character.SendMessage("ApplyDamage", damage * 1.5);
			}
			if (Character == null) {
				apply = false;
				Kill(true);
			}
		}
	}
}

function Kill (killed : boolean) { 
     if (killed) { 
          var headShot : AiHeadShot1 = GetComponent(AiHeadShot1); 
          if (headShot) { 
               Destroy(headShot); 
          } 
     } 
}

ps1: your character must:

  1. be a ragdoll (AI.js)
  2. have a head (AiHeadShot1.js)

ps2: your character’s head must:

  1. be rigidbody
  2. have a collider
  3. have an audio source

Actually, Destroy() handles nulls without any problem. Try putting this on something without a rigidbody.

function Start () {
	Destroy(GetComponent(Rigidbody));
}

It works without a null ref exception.

I think the problem is that you have a function called Destroy which then calls the builtin Destroy. Maybe it is somehow entering a recursive loop. I would try renaming your Destroy function.

You nailed it mr. The Destroy call inside the Destroy method would call the Destroy method defined in the class - not the built in one.

Whoops, I should have been reading more carefully!
You’re right, Carey and Ant. Let me know if I get this wrong, but it seems what’s happening is that when the Destroy() method is called with a MonoBehavior, you’re getting the desired behavior (Unity’s destroy method is called), but calling it with a null object is causing it to hit on the Destroy() defined in the script and treating the argument as a boolean? Which explains why it was “fixed” when Gaston tested against it. But it’s not really fixed. The lesson learned: avoid using reserved names, even if they have a slightly different signature.

No it’ll always call the local implementation - not the built in one.

Actually, it searches for matching method signature in the local scope and then travels upwards if there isn’t one.

So, if the local method would have two parameters, it would not match the signature and the builtin method would be called.
Also, if the call is Destroy(bool) the local implementation would be used and for Destroy(Object), the builtin one.

Since null isn’t any specific type, the method with one argument in the closes scope is called, which is the local method.

Okay, I already renamed it…
Now the function’s called function Kill (killed : boolean)

Thanks for help guys! :slight_smile: