'Trouble creating particle at a raycasts end/hit point for a moving object

Preamble:

I have a 3D side scroller style game in which the player flies along avoiding stuff, you know, side-scrollery things.

I'd like to add an effect (particle system) to the player when they get close (within a preset dangerZone, say 1.6 units (meters)) to the terrain, like a dusty dragging cloud under them sort of thing. I'm familiar with the particle system and raycasts but I don't know how to marry the concepts together to achieve what I'm after. The terrain undulates randomly and is not a flat surface, if that helps.

I'd also be hoping to make the particle system 'grow' the closer the player gets to the terrain if that makes sense. There is also speed to consider, so the closer to the ground and faster the player is should have an effect on the particle system.

My Thoughts:

I already have a score multiplier that uses a raycast to check the player's position from the ground/terrain and increases the closer they get.

void Update() {

    force = speed *2;

    RaycastHit hit;
    Ray downRay = new Ray(transform.position, -Vector3.up);

    if (Physics.Raycast(downRay, out hit, dangerZone)) {

        var distanceToGround = hit.distance;
        float hazardMultiplier = Mathf.Round( (transform.position.y - distanceToGround)*100 ) /100;

        if (hit.collider.tag == "Terrain") {
            playerData.scoreMulitplier = hazardMultiplier;
        }
    } 
    
    else {
        playerData.scoreMulitplier = playerData.baseScoreMulitplier;
    }
}

I'm thinking I can use the raycast I already have to instantiate a particle system on the terrain/at the raycast hit point but I'm not sure how exactly to go about this.

Any help is appreciated and thanks in advance.



Solution 1:[1]

You're on the right track. A couple things first before we dive into the solution:

  • Raycasts are calculated on the fixed frame (physics, FixedUpdate), not the visual frame (Update). While you may invoke a Raycast during Update, it won't be calculated until the next FixedFrame anyways. I'd recommend moving this to FixedUpdate to reduce the chance of doubled logic (2 simultaneous raycasts) or skipped logic (no raycasts).

  • You can set your particle system's scaling mode to the hierarchy, and scale using the transform. Alternatively, you can set the startSize of the particleSystem's main attributes.. Since you want to change the size of the particleSystem to change fairly frequently, I would recommend changing the scaling mode to hierarchy and just modifying the transform of the object it is attached to.

[SerializeField]
ParticleSystem ps;

[SerializeField]
float dangerZone = 1.6f;

[SerializeField]
float maxParticleSize = 2.0f; //How much you want particles to scale up based on closeness to terrain

void FixedUpdate() {
    RaycastHit hit;
    Ray downRay = new Ray(transform.position, -Vector3.up);

    //Consider adding a layerMask parameter here that only interacts with the "terrain" layer. This will save you physics computing power, and remove the need for tag checking every frame (which is not efficient).
    if (Physics.Raycast(downRay, out hit, dangerZone)) {

        var distanceToGround = hit.distance;
        float hazardMultiplier = Mathf.Round( (transform.position.y - distanceToGround)*100 ) /100; //This doesn't make too much sense to me, so you may want to revisit this. Since this isn't scoped for this question we can ignore it.

        //If you use a layerMask, then you wouldn't ever need to check 
        if (hit.collider.tag == "Terrain") {
            playerData.scoreMulitplier = hazardMultiplier;
            float particleScale = 1 + (dangerZone - hit.distance) / dangerZone;
            ps.transform.localScale = (particleScale, particleScale, particleScale);
            if(ps.isStopped)
                ps.Play();
        } else //In scenarios where the raycast hits a non-terrain target, you'll need to turn off particles. If you use a layermask this wouldn't be needed.
        {
            if(ps.isPlaying)
                ps.Stop();
        }
    } 
    
    else {
        if(ps.isPlaying)
            ps.Stop();
        playerData.scoreMulitplier = playerData.baseScoreMulitplier;
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Erik Overflow