Improved Tracer Effect

nixMWGD

New member
Hey everyone!
I didn't like that the tracers looked more like a laser than they look in most shooters so I decided to update it to move over time.

It's based on the older Call of Duty games that only used hitscan for weapons. Shots hit instantly but the tracer still travels over time as a visual effect.

UFPS%20Better%20Tracer.gif


1589087651225.png

This is the Opsive Tracer script with my changes. My demo uses

Code:
 public class Tracer : MonoBehaviour
    {
        public float m_VisibleTime;
        public float length;

        // Component references
        private Transform m_Transform;
        private LineRenderer m_LineRenderer;
        private Vector3 startPoint;
        private Vector3 endPoint;
        private float visibleTime;

        /// <summary>
        /// Initialize the default values.
        /// </summary>
        private void Awake()
        {           
            m_Transform = transform;
            m_LineRenderer = GetComponent<LineRenderer>();
        }

        private void OnEnable()
        {
            visibleTime = 0;
        }

        public void Initialize(Vector3 hitPoint)
        {
            startPoint = m_Transform.position;
            endPoint = hitPoint;

            Scheduler.Schedule(m_VisibleTime, DestroyObject);
        }

        private void Update()
        {
            visibleTime += Time.deltaTime;
            m_LineRenderer.SetPosition(0, Vector3.Lerp(m_Transform.position, endPoint, visibleTime / m_VisibleTime));
            m_LineRenderer.SetPosition(1, Vector3.Lerp(m_Transform.position + ((endPoint - m_Transform.position) * length), endPoint, visibleTime / m_VisibleTime));
        }

        private void DestroyObject()
        {
            ObjectPool.Destroy(gameObject);
        }
    }
 
Here's a progress update on it, going to clean up the script before I post it though:
Now uses AnimationCurve to adjust the size over distance so you can keep it visible even as it gets far away.
Also updated it to keep track of its position rather than going off of time so the speed is consistent - the previous version the speed changes based on hit distance.
Also added support for a secondary tracer effect to be spawned from the main tracer script for trails and smoke effects.

UFPS%20Better%20Tracer%202.gif
 
Forgot to post the updated script, here is the script used for the second .gif.

Added features:
*Speed is now consistent regardless of distance to target
*Uses three points with one point at 25% to give rain drop shape
*Option to flip the direction of the line renderer
*Ability to spawn a second tracer object that moves with the first for smoke trails and other trail effects
*Tracers have a set length, the tail stays at the fire point until the head is 'length' forward so it looks like it extends out of the firepoint


C#:
    /// <summary>
    /// The tracer will show a Line Renderer from the hitscan fire point to the hit point
    /// Updated by Nick F., MostWanted Game Development LLC
    /// </summary>
    public class Tracer : MonoBehaviour
    {
        [Tooltip("How long until the tracer is destroyed.")]
        public float m_VisibleTime;
        [Tooltip("distance per frame tracer effect moves.")]
        public float speed = 100f;
        [Tooltip("Length of tracer.")]
        public float length = .3f;
        [Tooltip("Max distance tracer will go if no hits.")]
        public float maxDistance = 100f;
        [Tooltip("Scale tracer from start to Max Distance. Use 0 to 1 for Time.")]
        public AnimationCurve scaleOverDistance;
        [Tooltip("Optional trail effect behind the tracer.")]
        public Tracer trail;
        [Tooltip("Flip tracer direction")]
        public bool flip;

        // Component references
        private Transform m_Transform;
        private LineRenderer m_LineRenderer;
        private Vector3 startPoint;
        private Vector3 endPoint;
        private float visibleTime;
        private float widthMultiplier;
        private float currentPosition;

        /// <summary>
        /// Initialize the default values.
        /// </summary>
        private void Awake()
        {          
            m_Transform = transform;
            m_LineRenderer = GetComponent<LineRenderer>();
            widthMultiplier = m_LineRenderer.widthMultiplier;
        }

        public void Initialize(Vector3 hitPoint)
        {
            startPoint = m_Transform.position;
            endPoint = hitPoint;
            currentPosition = 0f;
            m_LineRenderer.widthMultiplier = widthMultiplier;
            Scheduler.Schedule(m_VisibleTime, DestroyObject);
            if (trail)
            {
                Tracer newTrail = ObjectPool.Instantiate(trail.gameObject, transform.position, transform.rotation).GetComponent<Tracer>();
                newTrail.Initialize(hitPoint);
            }
        }

        private void Update()
        {
            // Move tracer forward
            currentPosition += speed * Time.deltaTime;
            float distance = Vector3.Distance(m_Transform.position, endPoint);
            // Calculate Head position
            m_LineRenderer.SetPosition((flip) ? 0 : 2, Vector3.Lerp(m_Transform.position, endPoint, currentPosition / distance));
            // Calculate Tail position
            Vector3 tail = (endPoint - m_Transform.position).normalized * -length;
            m_LineRenderer.SetPosition((flip) ? 2 : 0, (currentPosition - length > length) ? Vector3.Lerp(m_Transform.position + tail, endPoint, (currentPosition - length)/ distance) : m_Transform.position);
            // Middle point at 25% to give a raindrop shape to it
            m_LineRenderer.SetPosition(1, Vector3.Lerp(m_LineRenderer.GetPosition(0), m_LineRenderer.GetPosition(2), (flip) ? .25f : .75f));
            // Adjust line width as it gets farther from view
            m_LineRenderer.widthMultiplier = widthMultiplier * scaleOverDistance.Evaluate(Vector3.Distance(m_Transform.position, m_LineRenderer.GetPosition(0)) / maxDistance);
        }

        private void DestroyObject()
        {
            ObjectPool.Destroy(gameObject);
        }
    }
 
A Tracer that is not based on a LineRenderer and need a way to switch/extend the existing implementation.


C#:
    /// <summary>
    /// The tracer will show a Line Renderer from the hitscan fire point to the hit point
    /// Updated by Nick F., MostWanted Game Development LLC
    /// </summary>
    public class Tracer : MonoBehaviour //, ITracer
    {
        [Tooltip("How long until the tracer is destroyed.")]
        public float m_VisibleTime;
        [Tooltip("distance per frame tracer effect moves.")]
        public float speed = 100f;
        [Tooltip("Length of tracer.")]
        public float length = .3f;
        [Tooltip("Max distance tracer will go if no hits.")]
        public float maxDistance = 100f;
        [Tooltip("Scale tracer from start to Max Distance. Use 0 to 1 for Time.")]
        public AnimationCurve scaleOverDistance;
        [Tooltip("Optional trail effect behind the tracer.")]
        public Tracer trail;
        [Tooltip("Flip tracer direction")]
        public bool flip;

        // Component references
        private Transform m_Transform;
        private LineRenderer m_LineRenderer;
        private Vector3 startPoint;
        private Vector3 endPoint;
        private float visibleTime;
        private float widthMultiplier;
        private float currentPosition;

        /// <summary>
        /// Initialize the default values.
        /// </summary>
        private void Awake()
        {         
            m_Transform = transform;
            m_LineRenderer = GetComponent<LineRenderer>();
            widthMultiplier = m_LineRenderer.widthMultiplier;
        }

        public virtual void Initialize(Vector3 hitPoint)
        {
            startPoint = m_Transform.position;
            endPoint = hitPoint;
            currentPosition = 0f;
            m_LineRenderer.widthMultiplier = widthMultiplier;
            Scheduler.Schedule(m_VisibleTime, DestroyObject);
            if (trail)
            {
                Tracer newTrail = ObjectPool.Instantiate(trail.gameObject, transform.position, transform.rotation).GetComponent<Tracer>();
                newTrail.Initialize(hitPoint);
            }
        }

        private void Update()
        {
            // Move tracer forward
            currentPosition += speed * Time.deltaTime;
            float distance = Vector3.Distance(m_Transform.position, endPoint);
            // Calculate Head position
            m_LineRenderer.SetPosition((flip) ? 0 : 2, Vector3.Lerp(m_Transform.position, endPoint, currentPosition / distance));
            // Calculate Tail position
            Vector3 tail = (endPoint - m_Transform.position).normalized * -length;
            m_LineRenderer.SetPosition((flip) ? 2 : 0, (currentPosition - length > length) ? Vector3.Lerp(m_Transform.position + tail, endPoint, (currentPosition - length)/ distance) : m_Transform.position);
            // Middle point at 25% to give a raindrop shape to it
            m_LineRenderer.SetPosition(1, Vector3.Lerp(m_LineRenderer.GetPosition(0), m_LineRenderer.GetPosition(2), (flip) ? .25f : .75f));
            // Adjust line width as it gets farther from view
            m_LineRenderer.widthMultiplier = widthMultiplier * scaleOverDistance.Evaluate(Vector3.Distance(m_Transform.position, m_LineRenderer.GetPosition(0)) / maxDistance);
        }

        private void DestroyObject()
        {
            ObjectPool.Destroy(gameObject);
        }
    }
 
Has there been a change in UCC that makes this not work? Initialize never gets called. I had to subclass the tracer to Opsive.UltimateCharacterController.Objects.ItemAssist.Tracer. Then override Initialize(Vector3 hitPoint). I think I should also add that I am using Ultimate Inventory for the weapons if that makes a difference.
 
Top