I already have all of the VFX / damage working for a singular target, using raycasts and what not. I just can’t seem to figure out how to use a sphere collider to only detect ‘2’ (I would want this to be a configurable float) other enemies in a nearby radius that the lightning arcs too.
void ApplyDamageSpread()
{
Collider[] hitColliders = Physics.OverlapSphere(hitEnemy, damageRadius, LightningEnemyLayer);
List<Transform> listOfAllNearbyTransforms = new List<Transform>();
foreach (Collider c in hitColliders)
{
// i is a local variable that starts at 0; as long as i < 2f loop will happen ; i++ = add 1 to i
for (int i = 1; i < 2f; i++)
{
if (c.GetComponent<lightningEnemy>())
{
c.GetComponent<lightningEnemy>().Health -= damageSpread;
Transform transformOfCurrentCollider = c.transform;
listOfAllNearbyTransforms.Add(transformOfCurrentCollider);
}
}
}
}
I am learning and this is my first post, so expect this to make no sense, sorry if I formatted it wrong. The general idea I was going for was that in the for loop it would only look for 2 enemies, and then list the transforms of those enemies and then spawn the vfx ontop on the initial hit enemy and then also the enemies that were hit in the list and stored.
Two problems I encountered was that 1. The intiail hit enemy also gets affected by the sphere collider and takes damage. 2. How to actually transform the position of my vfx onto the position of the enemies that were hit by the collider.
I will post the shoot function that is calling the ApplyDamageSpread incase I am doing it entirely wrong.
public void shoot()
{
boltVFXClone = Instantiate(boltVFX, firePoint.transform.position, boltVFX.transform.rotation);
GameObject lightningStartVFX = GameObject.Find("LightningStart");
lightningStartVFX.transform.position = firePoint.transform.position;
lightningStartVFX.transform.parent = firePoint.transform;
Ray lightningRay = new Ray(playerCamera.position, playerCamera.forward);
if(Physics.Raycast(lightningRay, out RaycastHit hit, range))
{
if (hit.collider.gameObject.TryGetComponent(out lightningEnemy enemy))
{
GameObject lightningEndVFX = GameObject.Find("LightningEnd");
lightningEndVFX.transform.position = enemy.transform.position;
enemy.Health -= damage;
hitEnemy = enemy.transform.position;
ApplyDamageSpread();
if (enemy.Health <= 0f)
{
Destroy(lightningEndVFX, 1f);
Destroy(lightningStartVFX, 1f);
Destroy(boltVFXClone, 1f);
}
}
else
{
GameObject lightningEndVFX = GameObject.Find("LightningEnd");
lightningEndVFX.transform.position = hit.point;
}
}
else
{
GameObject lightningEndVFX = GameObject.Find("LightningEnd");
lightningEndVFX.transform.position = playerCamera.position + playerCamera.forward * range;
}
Destroy(lightningEndVFX, boltDuration);
Destroy(lightningStartVFX, boltDuration);
Destroy(boltVFXClone, boltDuration);
}
void ApplyDamageSpread()
{
Collider[] hitColliders = Physics.OverlapSphere(hitEnemy, damageRadius, LightningEnemyLayer);
List<Transform> listOfAllNearbyTransforms = new List<Transform>();
foreach (Collider c in hitColliders)
{
// i is a local variable that starts at 0; as long as i < 2f loop will happen ; i++ = add 1 to i
for (int i = 1; i < 2f; i++)
{
if (c.GetComponent<lightningEnemy>())
{
c.GetComponent<lightningEnemy>().Health -= damageSpread;
Transform transformOfCurrentCollider = c.transform;
listOfAllNearbyTransforms.Add(transformOfCurrentCollider);
}
}
}
}
You mean it gets hit twice? Or do you want to spare the direct hit enemy from damage?
In the former case simply don’t apply damage with the direct hit if the “look for surrounding enemies” check includes that target anyway. You would then always consider 3 enemies, not two.
You may also want to order the colliders by distance, so that you always hit the three closest ones. Distance is: var distance = (hitPosition - targetPosition).magnitude
That depends on your vfx. I assume applying the vfx to the enemy’s position is no issue, since you have their transform. As to make the lightning arc between the two enemies … hmmm you could trick this by simply spawning a new arc at the original position and “fire” it at the target position. If the arc fx is fast and/or enemies are slow you only need to consider the time it takes for the vfx to arrive, and then destroy it. Flight time would be distance divided by travel speed (in units per second).
An actual arc would require a vfx where you can specify an endpoint that particles travel to. Pretty sure there are properties in both particle systems.
The reason I want to seperate the initial damage and the colliders damage is because the initial damage will be greater than the spread. Is there a way of somehow not checking for the target that is hit first? Maybe I could use a boolean or something to see if it was initialhit?
Will take note, where would I work this in exactly?
The VFX is a prefab that I instantiate and then put on the transform.position of the enemy. However, what I mean by its not working is that I don’t know how to use a list to transform the vfx onto, if that makes sense? If I try to instantiate the vfx and just apply it to the lightningEnemy it only works for the first one but not all the other ones that the collider hits?
You can simply compare either the gameObject or transform to check if they are identical, and only if they are not, add the collider to the list:
if (initialHitTarget.gameObject != hit.gameObject)
Good question. Checking for closest distance is easy but getting the three closest is simply more complicated to explain.
One way would be rather than just adding colliders to the list, you insert them.
For every hitCollider you get its distance to the original target. Then you also for() loop over the nearby list entries and calculate their distance to the original target again. Only if the latter’s distance is greater than the current hitCollider’s distance, you insert the current collider at this index and break out of the loop. Doing it this way would guarantee that the first n entries in the list will be sorted in ascending order (closest is at index 0).
Sounds like you need to spawn a new VFX per enemy?
Often VFX are broken up into smaller parts. You could have an fx that’s simply put onto the target. Then you might have other vfx moving between two targets each.
Use NonAlloc version. You’ll get the n closest items, n being the size of the array you pass. As a bonus, you can reuse the array (declare it as a field) to avoid memory allocations.
No, there is no sorting done by the Overlap or *castAll functions. They’re either “in” the set, or not. With RaycastAll, it is at least giving you a RaycastHit structure for each hit, so you can find where the hits took place. Otherwise, how you want to sort is up to you: by tangent points? by centroids? by nearest point to center? The API has no idea of knowing nor desire to have the overhead.
Although it’s not documented, I always found in my usage that it returned the colliders sorted by distance. But yes, we can’t assume it’ll always do this or that Unity won’t change it.
Yeah, in terms of implementation, it’s quite likely that it will be in order, or almost in order, as the queries are probably done by traversing the octree cells in the direction of the cast. But as you point out, they might find another method or you might not be trying all possible query-affecting options like which “broadphase type” you have set.