using System.Collections; using System.Collections.Generic; using UnityEngine; namespace XftWeapon { public class XWeaponTrail : MonoBehaviour { public class Element { public Vector3 PointStart; public Vector3 PointEnd; public Vector3 Pos { get { return (PointStart + PointEnd) / 2f; } } public Element(Vector3 start, Vector3 end) { PointStart = start; PointEnd = end; } public Element() { } } public class ElementPool { private readonly Stack _stack = new Stack(); public int CountAll { get; private set; } public int CountActive { get { return CountAll - CountInactive; } } public int CountInactive { get { return _stack.Count; } } public ElementPool(int preCount) { for (int i = 0; i < preCount; i++) { Element element = new Element(); _stack.Push(element); CountAll++; } } public Element Get() { Element element; if (_stack.Count == 0) { element = new Element(); CountAll++; } else { element = _stack.Pop(); } return element; } public void Release(Element element) { if (_stack.Count > 0 && ReferenceEquals(_stack.Peek(), element)) { Debug.LogError("Internal error. Trying to destroy object that is already released to pool."); } _stack.Push(element); } } #region public members public static string Version = "1.4.3"; public bool UseWith2D = false; public bool UseWithSRP = false; public string SortingLayerName; public int SortingOrder; public Transform PointStart; public Transform PointEnd; public int MaxFrame = 14; public int Granularity = 60; public float Fps = 60f; public Color MyColor = Color.white; public Material MyMaterial; #endregion #region protected members protected float mTrailWidth = 0f; protected Element mHeadElem = new Element(); protected List mSnapshotList = new List(); protected ElementPool mElemPool; protected Spline mSpline = new Spline(); protected float mFadeT = 1f; protected bool mIsFading = false; protected float mFadeTime = 1f; protected float mElapsedTime = 0f; protected float mFadeElapsedime = 0f; protected GameObject mMeshObj; protected VertexPool mVertexPool; protected VertexPool.VertexSegment mVertexSegment; protected bool mInited = false; protected bool mActivated = false; #endregion #region property public float UpdateInterval { get { return 1f / Fps; } } public Vector3 CurHeadPos { get { return (PointStart.position + PointEnd.position) / 2f; } } public float TrailWidth { get { return mTrailWidth; } } protected Camera _myCamera; public Camera MyCamera { get { if (_myCamera == null) { _myCamera = FindMyCamera(); } return _myCamera; } } protected Camera FindMyCamera() { int layerMask = 1 << gameObject.layer; //Camera[] cameras = GameObject.FindObjectsOfType(typeof(Camera)) as Camera[]; Camera[] cameras = Camera.allCameras; for (int i = 0, imax = cameras.Length; i < imax; ++i) { Camera cam = cameras[i]; if ((cam.cullingMask & layerMask) != 0) { return cam; } } Debug.LogError("can't find proper camera for layer:" + gameObject.layer); return null; } #endregion #region API //you may pre-init the trail to save some performance. public void Init() { if (mInited) return; mElemPool = new ElementPool(MaxFrame); mTrailWidth = (PointStart.position - PointEnd.position).magnitude; InitMeshObj(); InitOriginalElements(); InitSpline(); mInited = true; } public void Activate() { if (mActivated) { return; } Init(); mActivated = true; gameObject.SetActive(true); //mVertexPool.SetMeshObjectActive(true); mFadeT = 1f; mIsFading = false; mFadeTime = 1f; mFadeElapsedime = 0f; mElapsedTime = 0f; //reset all elemts to head pos. for (int i = 0; i < mSnapshotList.Count; i++) { mSnapshotList[i].PointStart = PointStart.position; mSnapshotList[i].PointEnd = PointEnd.position; mSpline.ControlPoints[i].Position = mSnapshotList[i].Pos; mSpline.ControlPoints[i].Normal = mSnapshotList[i].PointEnd - mSnapshotList[i].PointStart; } //reset vertex too. RefreshSpline(); UpdateVertex(); RefreshShader(); } public void RefreshShader() { MyMaterial.shader = Shader.Find(MyMaterial.shader.name); } public void Deactivate() { mActivated = false; gameObject.SetActive(false); mVertexPool.SetMeshObjectActive(false); } public void StopSmoothly(float fadeTime) { mIsFading = true; mFadeTime = fadeTime; } #endregion #region unity methods void OnEnable() { Activate(); if (!UseWithSRP) { Camera.onPostRender += MyPostRender; Camera.onPreRender += MyPreRender; } } void OnDisable() { // Deactivate(); if (!UseWithSRP) { Camera.onPostRender -= MyPostRender; Camera.onPreRender -= MyPreRender; } } // void Update() { // if (!UseWithSRP) { // return; // } // MyPreRender(MyCamera); // } void LateUpdate() { if (!UseWithSRP) { return; } MyPreRender(MyCamera); MyPostRender(MyCamera); } public void MyPreRender(Camera cam) { if (!mInited) return; if (cam != MyCamera) { return; } UpdateHeadElem(); mElapsedTime += Time.deltaTime; if (mElapsedTime > UpdateInterval) { mElapsedTime = 0f; RecordCurElem(); } RefreshSpline(); UpdateFade(); UpdateVertex(); } public void MyPostRender(Camera cam) { if (!mInited) return; if (cam != MyCamera) { return; } mVertexPool.SetMeshObjectActive(true); mVertexPool.LateUpdate(); } void OnDestroy() { if (!mInited || mVertexPool == null) { return; } mVertexPool.Destroy(); } void Start() { mInited = false; Init(); } void OnDrawGizmos() { if (PointEnd == null || PointStart == null) { return; } float dist = (PointStart.position - PointEnd.position).magnitude; if (dist < Mathf.Epsilon) return; Gizmos.color = Color.red; Gizmos.DrawSphere(PointStart.position, dist * 0.04f); Gizmos.color = Color.blue; Gizmos.DrawSphere(PointEnd.position, dist * 0.04f); } #endregion #region local methods void InitSpline() { mSpline.Granularity = Granularity; mSpline.Clear(); for (int i = 0; i < MaxFrame; i++) { mSpline.AddControlPoint(CurHeadPos, PointStart.position - PointEnd.position); } } void RefreshSpline() { for (int i = 0; i < mSnapshotList.Count; i++) { mSpline.ControlPoints[i].Position = mSnapshotList[i].Pos; mSpline.ControlPoints[i].Normal = mSnapshotList[i].PointEnd - mSnapshotList[i].PointStart; } mSpline.RefreshSpline(); } void UpdateVertex() { VertexPool pool = mVertexSegment.Pool; for (int i = 0; i < Granularity; i++) { int baseIdx = mVertexSegment.VertStart + i * 3; float uvSegment = (float) i / Granularity; float fadeT = uvSegment * mFadeT; Vector2 uvCoord = Vector2.zero; Vector3 pos = mSpline.InterpolateByLen(fadeT); //Debug.DrawRay(pos, Vector3.up, Color.red); Vector3 up = mSpline.InterpolateNormalByLen(fadeT); Vector3 pos0 = pos + (up.normalized * mTrailWidth * 0.5f); Vector3 pos1 = pos - (up.normalized * mTrailWidth * 0.5f); // pos0 pool.Vertices[baseIdx] = pos0; pool.Colors[baseIdx] = MyColor; uvCoord.x = 0f; uvCoord.y = uvSegment; pool.UVs[baseIdx] = uvCoord; //pos pool.Vertices[baseIdx + 1] = pos; pool.Colors[baseIdx + 1] = MyColor; uvCoord.x = 0.5f; uvCoord.y = uvSegment; pool.UVs[baseIdx + 1] = uvCoord; //pos1 pool.Vertices[baseIdx + 2] = pos1; pool.Colors[baseIdx + 2] = MyColor; uvCoord.x = 1f; uvCoord.y = uvSegment; pool.UVs[baseIdx + 2] = uvCoord; } mVertexSegment.Pool.UVChanged = true; mVertexSegment.Pool.VertChanged = true; mVertexSegment.Pool.ColorChanged = true; } void UpdateIndices() { VertexPool pool = mVertexSegment.Pool; for (int i = 0; i < Granularity - 1; i++) { int baseIdx = mVertexSegment.VertStart + i * 3; int nextBaseIdx = mVertexSegment.VertStart + (i + 1) * 3; int iidx = mVertexSegment.IndexStart + i * 12; //triangle left pool.Indices[iidx + 0] = nextBaseIdx; pool.Indices[iidx + 1] = nextBaseIdx + 1; pool.Indices[iidx + 2] = baseIdx; pool.Indices[iidx + 3] = nextBaseIdx + 1; pool.Indices[iidx + 4] = baseIdx + 1; pool.Indices[iidx + 5] = baseIdx; //triangle right pool.Indices[iidx + 6] = nextBaseIdx + 1; pool.Indices[iidx + 7] = nextBaseIdx + 2; pool.Indices[iidx + 8] = baseIdx + 1; pool.Indices[iidx + 9] = nextBaseIdx + 2; pool.Indices[iidx + 10] = baseIdx + 2; pool.Indices[iidx + 11] = baseIdx + 1; } pool.IndiceChanged = true; } void UpdateHeadElem() { mSnapshotList[0].PointStart = PointStart.position; mSnapshotList[0].PointEnd = PointEnd.position; } void UpdateFade() { if (!mIsFading) return; mFadeElapsedime += Time.deltaTime; float t = mFadeElapsedime / mFadeTime; mFadeT = 1f - t; if (mFadeT < 0f) { Deactivate(); } } void RecordCurElem() { //TODO: use element pool to avoid gc alloc. //Element elem = new Element(PointStart.position, PointEnd.position); Element elem = mElemPool.Get(); elem.PointStart = PointStart.position; elem.PointEnd = PointEnd.position; if (mSnapshotList.Count < MaxFrame) { mSnapshotList.Insert(1, elem); } else { mElemPool.Release(mSnapshotList[mSnapshotList.Count - 1]); mSnapshotList.RemoveAt(mSnapshotList.Count - 1); mSnapshotList.Insert(1, elem); } } void InitOriginalElements() { mSnapshotList.Clear(); //at least add 2 original elements mSnapshotList.Add(new Element(PointStart.position, PointEnd.position)); mSnapshotList.Add(new Element(PointStart.position, PointEnd.position)); } void InitMeshObj() { //init vertexpool mVertexPool = new VertexPool(MyMaterial, this); mVertexSegment = mVertexPool.GetVertices(Granularity * 3, (Granularity - 1) * 12); UpdateIndices(); } #endregion } }