Coroutines Yield

Hello community,

I’m quite new to Unity3D and having some problems with my latest piece of script.
But first of all some explanation!
I’m using it to simulate the ballistics of any fast moving projctile in 3D space that can’t be realized by using Rigidbodies. In fact it does something very simmilar to the normal raycasting one would use for shooting. But in way of real world’s ballistics being slightly more complex, especially for great distances, it does not only create one straight raycast, but many of them based on the formula of an angular throw. As this procedure as a bit complicated to describe (and even to understand :lol: ), pardon my idleness of giving further details! :wink:
The only thing important to know is, that I want my script to simulate the trajectory of my bullets physically correct and in this way let them take some time to reach a target.
The problem is that a Raycast Hit is returned immediately as if the bullet would fly with speed of light. To solve this I let my script calculate the time a projectile with a certain velocity would need to cover the distance to the target and then do a “yield WaitForSeconds()” before actually casting the ray.
Well everything seemed to work perfect: The time is calculated correctly, the the raycast is done and there is a delay between “pulling the trigger” and RaycastHit.
But for any reason this delay is far to big :!: :?: :x
For example a bullet with a velocity of 1000 units per second would take about 3 seconds to cover a distance of 100 meters! Eigther my maths is even worse than I’ve thought or there is something wrong with the “yield” class :lol: :cry:

Can anyone out there help me please!?

Thanks in advance for any productive kind of help!

Now here is the whole code. Hope it won’t slay you:

var start : Transform;
var inaccuracy : float = 0.1;
var smokeMaterial : Material;
var startColor : Color = Color (1.0, 1.0, 1.0, 1.0);
var endColor : Color = Color (1.0, 1.0, 1.0, 0.0);
var projectileVelocity : float = 10.0;
var bulletHole : Transform;
var exitHole : Transform;
var fireDelay : float;
var shotSound : AudioClip;
var solvingInterval : float = 1.0;
var solvingCount : int = 100;
var leaveTracer : boolean = true;
var damage : float =1.0;

private var nextFire = 0.0;
private var player : Transform;
private var muzzleFlash;

function Awake()
{
	player = GameObject.Find("First Person Controller").GetComponent(Transform);
	muzzleFlash = GameObject.Find("First Person Controller/Main Camera/ACOG/lauf/MuzzleFlash").particleEmitter;
}

function Update () 
{
	if (Input.GetButton("Fire1"))
	{
		if (Time.time > nextFire)
		{
			nextFire = Time.time + fireDelay;
			Shoot();
		}
	}
}

function Shoot()
{
	audio.PlayOneShot(shotSound);
	muzzleFlash.Emit (1);
	
	var startPoint : Vector3 = start.transform.position;
	var lastPoint : Vector3 = startPoint;
	
	var yAngle : float = player.transform.rotation.eulerAngles.y;

	var offsetX : float = solvingInterval * Mathf.Sin(yAngle * Mathf.Deg2Rad);
	var offsetZ : float = solvingInterval * Mathf.Cos(yAngle * Mathf.Deg2Rad);

	if (transform.rotation.eulerAngles.x >= 180)
	{
		var xAngle = (360 - transform.rotation.eulerAngles.x);
	}
	else
	{
		xAngle = -(transform.rotation.eulerAngles.x);
	}
	
	if (leaveTracer == true)
	{
		line = new GameObject ("SmokeEffect");		
	
		fadeout = line.AddComponent("AlphaFadeout");
		fadeout.fadeSpeed = 0.1;
	
		visual = line.AddComponent("LineRenderer");
		visual.useWorldSpace = true;
		visual.SetWidth (0.01, 0.2);
		visual.material = smokeMaterial;
		visual.SetColors (startColor, endColor);
	
		visual.material.SetTextureScale ("_MainTex", Vector2(10, 1));
	}

	var missX : float = Random.Range(-inaccuracy, inaccuracy);
	var missY : float = Random.Range(-inaccuracy, inaccuracy);
	var missZ : float = Random.Range(-inaccuracy, inaccuracy);

	for (i=0; i<solvingCount; i++)
	{												
		var timeToTarget : float = ((i * solvingInterval/(projectileVelocity * (Mathf.Cos(xAngle * Mathf.Deg2Rad)))) - ((i - 1) * solvingInterval/(projectileVelocity * (Mathf.Cos(xAngle * Mathf.Deg2Rad)))));				

		yield WaitForSeconds(timeToTarget);

		var offsetY : float = (-(-Physics.gravity.y/2) * (Mathf.Pow (i * solvingInterval, 2)/(Mathf.Pow (projectileVelocity, 2) * Mathf.Pow (Mathf.Cos (xAngle * Mathf.Deg2Rad), 2))) + i * solvingInterval * Mathf.Tan (xAngle * Mathf.Deg2Rad)) + i * missY;
		
		var nextPoint : Vector3 = Vector3(startPoint.x + i * (offsetX + missX), startPoint.y + offsetY + i * missY, startPoint.z + i * (offsetZ + missZ));
		
		var dis : float = Vector3.Distance (lastPoint, nextPoint);
		
		// Cast a Ray forward for impact holes
		
		var fRay = new  Ray (lastPoint, Vector3((nextPoint.x - lastPoint.x), (nextPoint.y - lastPoint.y), (nextPoint.z - lastPoint.z)));
		var fHits : RaycastHit[];
		fHits = Physics.RaycastAll (fRay, dis);

		for (var f=0; f<fHits.length; f++)
		{
			var fHit : RaycastHit = fHits[f];
			var fOtherObj : GameObject = fHit.collider.gameObject;
			var fHitPoint = fHit.point;				
			var fHitRotation = Quaternion.FromToRotation(Vector3.up, fHit.normal);	
			var fHole = Instantiate(bulletHole, fHitPoint, fHitRotation);
			fHole.transform.parent = fHit.transform;
			
			if (fOtherObj.collider.attachedRigidbody)
			{				
				var direction = Vector3((nextPoint.x - lastPoint.x), (nextPoint.y - lastPoint.y), (nextPoint.z - lastPoint.z));
				fOtherObj.collider.rigidbody.AddForceAtPosition(direction.normalized * 1000, fHitPoint);
			}
			
			if (fOtherObj.GetComponent(ObjectDestruction))
			{
				fOtherObj.GetComponent(ObjectDestruction).objectLife -= damage;
			}
		}
		
		// Cast a Ray back for exit holes
		
		var bRay = new  Ray (nextPoint, Vector3((lastPoint.x - nextPoint.x), (lastPoint.y - nextPoint.y), (lastPoint.z - nextPoint.z)));
		var bHits : RaycastHit[];
		bHits = Physics.RaycastAll (bRay, dis);

		for (var b=0; b<bHits.length; b++)
		{
			var bHit : RaycastHit = bHits[b];
			var bOtherObj : GameObject = bHit.collider.gameObject;
			var bHitPoint = bHit.point;				
			var bHitRotation = Quaternion.FromToRotation(Vector3.up, bHit.normal);	
			var bHole = Instantiate(exitHole, bHitPoint, bHitRotation);
			bHole.transform.parent = bHit.transform;				
		}
		
		if (leaveTracer == true)
		{
			visual.SetVertexCount (i + 1);
			visual.SetPosition(i, nextPoint);
		}
		
		lastPoint = nextPoint;
	}
}

If you use yield return 0; to wait a single frame.

That way you can do your raycasts and just interpolate on a frame per frame basis, without unusual timing effects.

For example:
1 You set a speed 100 m/s
2 You fire your shot and save point A to the Muzzle
3 Wait a Frame
4 Get Point B (Point A + speed * Time.deltaTime + Gravity * Time.deltaTime) (speed is for X value, Gravity is for Y value)
5 Do a Raycast from Point A towards Point B at their distance
6 if you hit something mark it and end
7 if not set point A to Point B and go back to step 3

I think that would greatly simplify the code. The only caveat is that you may need to do some overlap within your raycasts as a second fast moving target the other way could skip through it if he was incredibly lucky.

However, if you want to use your existing method it looks like your issue is either in calculating timeToTarget or the fact that you seem to be waiting the entire time to reach your target with every solving count(if it takes 10 rays. timeToTarget is waited for 10 times, as it’s within the for loop).

Although to be honest I don’t fully follow the math being done without explanations and am going mostly off variable names.

Edit: Also, another issue is that SolvingCount is 100, and therefore you call WaitForSeconds 100 times. WaitForSeconds is not a thread and is not actually Async, and therefore can only round to the nearest frame. That means that if you have a framerate of 33 the fastest it can possibly process is 3 seconds (100/33 = ~3).

Yeah thanks for your reply!

But in fact the method I use is exactly what you are explaining, except my formula is a bit more complex than yours as it implies the angle at which the bullet is shot off! I’m generating a few Vector3s and casting rays between them returning all raycast hits. But don’t mind the way I’m doing the raycast calculation, just try the script out if you want to see how it works! :smile:

The real problem is that none of the reasons you mentioned is responsible for the delay being to great!
It’s neigther the calculation of the timeToTarget (print(timeToTarget)will give you the correct value), nor am I waiting the entire time to reach the target(timeToTarget will just return the time for a single rayast between point A and B)! :cry:
It’s just too crazy, the delay seems to stay the same, independent of the velocity I recently recognized :shock:

Your theory about the framerate-dependence seems to make sense but is there any way to solve it in JavaScript???

Did you read my edit?

Try lowering the Solver count, or making the solver count dependent on distance (you can use a While/break pair with a yield inside to solve until you hit what you want).

Yield WaitForSeconds has a built in minimum wait time that it can’t go under, and that is directly related to your framerate.

Edit again: I only read that last bit now.
You can also use WaitForFixedUpdate() to be able to guarantee a specific framerate and scale your solver count within that. But I think that having a large set amount of iterations by default is going to cause issues with such small timesteps.

I still don’t understand the complex calculations.

Regardless of the direction of the shot, once it has left the muzzle the forces acting on it are quite simple.

You can separate Projectile motion into independent X and Y axes, regardless of direction of fire.

X velocity remains permanently unchanged. (Newton’s First Law of Motion)
Y velocity lowers relative to the acceleration due to gravity.

No matter which direction you fire, projectile physics will work the same. The only force acting on it once it’s left the gun is gravity and that is always directly downwards.

	//Velocity = the Shooters .forward vector
	public IEnumerator Shoot(Vector3 velocity, Vector3 muzzle)
	{
		Vector3 distance;
		Vector3 pointA = muzzle;
		Vector3 pointB;
		
		while(true)
		{
			velocity.y += Vector3.down * Physics.gravity * Time.deltaTime;
			pointB = pointA + (velocity * Time.deltaTime);
			distance = pointB - pointA;
			Debug.DrawLine(pointA, pointA + distance);
			RaycastHit[] hits;
			if(Physics.RaycastAll(pointA, distance.normalized, out hits, distance.magnitude))
			{
				//Do Hit Stuff here
				break;
			}
			pointA = pointB;
			yield return 0;
		}
	}

Would accomplish the same task much less strenuously on the processor in a Framerate independent manner.

I don’t use Unity’s Javascript often, but you could try:
yield;
yield 0;
one of them could work for a single frame.