Simulating proper projectile travel time using Raycast

Hi all. I’m currently working on a tank game in order to become more familiar with Unity. In the game, there will be projectiles moving at high speeds (1,500 m/s or more). At first, I was content with using Raycasts to represent these high speed shots, but as engagement ranges slowly expanded to 2-3 km, discrepancies began to occur.

First of all, I use a modified version of the TrajectorySimulation, and CalculateLeadForProjectiles scripts on the wiki along with a modified CalculateElevation function I kitbashed together from various sources. I don’t have the modified scripts with me right now as I am using a different computer. I’ll try to post them as soon as I can.

To the main question: Is there a way to properly simulate travel time while retaining the use of Raycasts?

Attached is an illustration of my current methods and problem

I have tried adding a delay in between Raycasts via yield WaitForSeconds(TimeProjectileMoves1Meter) but this only results in the simulation slowing to an unacceptable crawl, most likely limited by Update.

Thoughts on the matter would be most appreciated. :slight_smile:

UPDATE: here is my code for firing the gun:

static function ShootProjectile(bullet:Projectile,muzzle:Transform,velocity:Vector3) {
	var numVertices : int = 3000;
	var positions = new Vector3[numVertices];
    // The first line point is wherever the player's cannon, etc is
    positions[0] = muzzle.position;
 
    // Time it takes to traverse one segment of length segScale
    var segmentScale : float = 1.0;
    var segTime : float;
 
    // The velocity of the current segment
    var segVelocity : Vector3 = (muzzle.forward * bullet.speed) + (velocity);
 
    var hit : RaycastHit;
 
    for (var i=1; i<numVertices; i++) {
//    	Debug.Log(segTime);
        // worry about if velocity has zero magnitude
        if(segVelocity.sqrMagnitude != 0)
            segTime = segmentScale/segVelocity.magnitude;
        else
            segTime = 0;
        // Add velocity from gravity for this segment's timestep
        segVelocity = segVelocity + Physics.gravity*segTime;
 
        // Check to see if we're going to hit a physics object
        if(Physics.Raycast(positions[i-1], segVelocity, hit, segmentScale)){
        	Debug.DrawLine (positions[i-1], hit.point, Color.red,30,false);
            // set next position to the position where we hit the physics object
            positions _= positions[i-1] + segVelocity.normalized*hit.distance;_
  •  	ballistics.HitProjectile(segTime,positions,i,segVelocity,segmentScale,bullet);*
    

return;

  • }*
    // If our raycast hit no objects, then set the next position to the last one plus v*t
    else {
    positions = positions[i-1] + segVelocity*segTime;
    _ Debug.DrawLine (positions[i-1], positions[i-1] + segVelocitysegTime, Color.red,30,false);_
    _
    }_
    _
    }_
    _
    }*_

static function HitProjectile(time:float,positions:Vector3[],length:int,velocity:Vector3,scale:float,bullet:Projectile) {
* for (var i = 1; i < length+2; i++) {*
* var hit : RaycastHit;*
* if(Physics.Raycast(positions[i-1], velocity, hit, scale)){*
* Debug.DrawLine (positions[i-1], hit.point, Color.yellow,30,false);*
* var boom = new Instantiate(bullet.explosion,hit.point,Quaternion.identity);*
* var hitInfo : BulletHit = new BulletHit(20,10,hit.point);*
* hit.transform.root.SendMessage(“TakeDamage”,hitInfo,SendMessageOptions.DontRequireReceiver);*
* return;*
* }*
* else {*
* Debug.DrawLine (positions[i-1], positions[i-1]+velocity, Color.yellow,30,false);*
* }*
yield WaitForSeconds(time);
* }*
* Debug.Log(“No hit?”); *
* }*
As you can see, at the start of the for loop, segTime is defined as unit (segmentScale) divided by segVelocity (speed). The yield wait is in the HitProjectile function where I believed it would delay casting the next ray by the time it takes the bullet to move 1 segment.
To clarify, I am looking for a way to add a delay in between the Rays casted in the HitProjectile function. This should simulate the actual time it takes for the bullet to travel across the previously calculated trajectory in ShootProjectile.
The script is given the tank’s equipped bullet type, muzzle Transform and current velocity.

I’ll post this here in case any other noobs like me get the same problem.

Projectile.js

#pragma strict

public var speed : float;
public var trace : Transform;
public var explosion : GameObject;
public var explosionAudio : AudioClip;

public var penetration : int = 20;
public var damage : int = 10;

public var layerMask : LayerMask; //make sure we aren't in this layer
private var previousPos : Vector3;
private var thisPos : Vector3;
private var stepDirection;
private var stepSize;

function Awake() { 
	}

function Start () {
	
	Destroy(this.gameObject,10);
	Shoot();
	
	}

function Update () {

	}
	
function FixedUpdate() { 
	var hitInfo : RaycastHit;
	thisPos =  transform.position;
	stepDirection = this.rigidbody.velocity.normalized;
	stepSize = (thisPos - previousPos).magnitude;
	if (stepSize > 0.1) {
		if (Physics.Raycast(previousPos, stepDirection, hitInfo, stepSize, layerMask)) {
			Destruct(hitInfo.point,hitInfo.normal,hitInfo.transform.root);
			}
		else {
			previousPos = thisPos;
			}
		}
	}
	
function Destruct(point:Vector3,normal:Vector3,target:Transform) {
	var hitNormal = Quaternion.Euler(normal);
	var boom = new Instantiate(explosion,point,hitNormal);
	AudioSource.PlayClipAtPoint(explosionAudio,transform.position,2f);
	Destroy(this.gameObject);
	}

function Shoot () {

	previousPos = this.transform.position;
	this.rigidbody.AddForce(this.transform.forward * speed, ForceMode.VelocityChange);
	
	}

So finally got around this by going back to using instantiated prefabs as projectiles and then modifying the DontGoThroughThings script on the wiki.

For those who don’t know about it, it essentially checks the area behind the projectile every frame using a raycast. If the raycast hits something, it teleports the rigidbody to the hit point.

Unfortunately, this caused a bit of a problem since it would most likely pick up the collider it passed through last, leading to one-shot killing tanks from the front or invulnerable rear armor. So what I did was change the direction of the cast. Instead of going backwards, the ray is shot from the projectile’s previous position. Works flawlessly for me.

I just instantiate a bullet prefab, the rigidbody calculates the drop and collisions automatically. Plus, you can add the bullet’s mesh to that prefab.