0
\$\begingroup\$

I have a class called SpriteMeshModifier, which is a MonoBehavior. Since the rest of my script depends on this class being a MonoBehavior, I have trouble deciding how to translate the bulky updating logic for vertices into an ECS Entity. Chat GPT mentioned that I should "MonoBehaviour Logic: Since we are moving to ECS, the logic in your Vertex MonoBehaviour should be transferred to ECS Systems. Make sure to disable or remove the MonoBehaviour scripts to avoid duplicate logic." Thus, I am having trouble how to integrate this tutorial: https://coffeebraingames.wordpress.com/2021/11/23/the-safest-way-to-use-dots-in-your-monobehaviour-project/ How should I go about it?

using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using SoftBody2D;

namespace HerminkasGams.SpriteSquash
{
    [System.Serializable]
    public class SpriteMeshModifier : MonoBehaviour
    {
        public Sprite _sprite;
        public Material material;
        public GameObject Vertex;
        public GameObject ObjectManager;
        [HideInInspector]public ObjectManagerController objectManagerController;

        [HideInInspector]
        public Color Color;          // Colour of material

        [HideInInspector]
        public int sortingLayerID;   // Sorting layer of mesh renderer

        [HideInInspector]
        public int sortingOrder;     // Sorting order of mesh renderer

        [HideInInspector]
        public float refine = 0.5f;  // Refine defines how dense will be created mesh
        [HideInInspector]
        public float forceImpact;   // Force impact is how strong is selected vertex impacting other vertices
        [HideInInspector]
        public float sizeImpact;    // Size impact is radius around selected vertex, this radius is selecting vertices which will be imacted
        [HideInInspector]
        public float stiffnes = 1;  // Defines how vertices are stiff - in other words how fast they can get to their initial position
        [HideInInspector]
        public float revert = 1;    // Revert is amount in lerp function to initial state of mesh

        [HideInInspector]
        public List<KeyValuePair<int, int>> trianglesLines = new List<KeyValuePair<int, int>>();    // Lines holding triangles's lines 

        [HideInInspector]
        public bool _isCustomShape;   // Button holds whether generated mesh is customed

        [HideInInspector]
        public bool _isSelfRestoring; // Self restoring mesh is that vertices are returning to their initial state

        [HideInInspector]
        public List<Vertex> vertices = new List<Vertex>();  // List of vertices; each element of this variable is what I must make magnetic

        public float HandleRadious; // Handle radious

        public void Awake() {
            // _sprite = gameObject.GetComponent<SoftSprite>().Sprite;
            //Search starts from Resources folder
            // Vertex = Resources.Load<GameObject>( "../Herminka's gams/Prefabs/Vertex" );
            ObjectManager = GameObject.Find("ObjectManager");
            objectManagerController = ObjectManager.GetComponent<ObjectManagerController>();
        }
      

        private void Update()
        {
            UpdateMesh();
            foreach (var vertex in vertices)
            {
                if (objectManagerController.metaballEdgePointVertices.Count > 0)
                {
                    // Assume FindNearestMagnetPoint() is now a public method in Vertex
                    //Vector3 nearestMagnetPoint = vertex.FindNearestMagnetPoint();
                    Debug.Log("Current vertex position: ");
                    // Debug.Log(vertex.currentPos);
                    // Debug.Log("Found nearest magnet point for current mesh vertex!");
                    // Debug.Log(nearestMagnetPoint);
                    // Update the vertex position
                    //vertex.UpdateCurrentVertex(nearestMagnetPoint, vertex._index);
                }
            }
        }


        private void AssignMesh(IMesh meshGenerator)    // Generates and assignes mesh to mesh filter 
        {
      
            GetComponent<MeshFilter>().sharedMesh = meshGenerator.GenerateMesh();
           GetComponent<MeshRenderer>().sharedMaterial = new Material(material);
            GetComponent<MeshRenderer>().sharedMaterial.mainTexture = _sprite.texture;
            
        }

        private void GenerateVerteciesList()             // Generates and initialises vertices
        {
            foreach (var item in GetComponentsInChildren<Vertex>())
            {
                DestroyImmediate(item.gameObject);
            }
            vertices.Clear();
            
            int cntr = 0;
            float circleColliderRadius = 10.0f;

            foreach (var vertex in GetComponent<MeshFilter>().sharedMesh.vertices.ToList())
            {
                var go = Instantiate(Vertex, transform);
                go.name = go.name + " " + cntr;

                // Add CircleCollider2D
                CircleCollider2D circleCollider = go.AddComponent<CircleCollider2D>();
                // Configure CircleCollider2D properties
                circleCollider.radius = circleColliderRadius;
                circleCollider.isTrigger = true;

                // Rigidbody2D rigidbody2D = go.AddComponent<Rigidbody2D>();
                // rigidbody2D.gravityScale = 0;

                var ver = go.GetComponent<Vertex>();
                ver.SetInitialPos(vertex, cntr, go, this, objectManagerController); //this is reference to SpriteMeshModifier object that owns vertices
                vertices.Add(ver);
                cntr++;
            }
        }

        public void UpdateMesh()                        // Updates Mesh
        {
            Mesh mesh = GetComponent<MeshFilter>().sharedMesh;
            mesh.vertices = vertices.Select(x => x.currentPos).ToArray();
            GetComponent<MeshFilter>().sharedMesh = mesh;
        }

        public void ExtractMeshLines()                  // Extract lines from mesh, so it can be displayed in scene view
        {
            List<string> edges = new List<string>();
            trianglesLines.Clear();
            int[] triangles = GetComponent<MeshFilter>().sharedMesh.triangles;

            for (int i = 0; i < triangles.Length; i += 3)
            {
                for (int e = 0; e < 3; e++)
                {
                    int vert1 = triangles[i + e];
                    int vert2 = triangles[i + e + 1 > i + 2 ? i : i + e + 1];
                    string edge = Mathf.Min(vert1, vert2) + ":" + Mathf.Max(vert1, vert2);
                    if (!edges.Contains(edge))
                    {
                        edges.Add(edge);
                        trianglesLines.Add(new KeyValuePair<int, int>(vert1, vert2));
                    }
                }
            }
        }

        public void UpdateVertecies(Vector3 moveTo, int index)    //  Update vertices
        {
            moveTo = Quaternion.Inverse(transform.rotation) * moveTo;
            Vector3 newMousePosition = new Vector3(moveTo.x / transform.localScale.x, moveTo.y / transform.localScale.y, moveTo.z / transform.localScale.z)
                  - Quaternion.Inverse(transform.rotation) * new Vector3(transform.position.x / transform.localScale.x, transform.position.y / transform.localScale.y, transform.position.z / transform.localScale.z);

            UpdatePositionOfVertices(moveTo, newMousePosition - vertices[index].currentPos);
            UpdatePositionOfSelectedVertex(index, newMousePosition);
            UpdateMeshFromEditor();
        }

        private void UpdatePositionOfSelectedVertex(int index, Vector3 pos) // Update position of selected vertex
        {
            vertices[index].currentPos = pos;
        }

        private void UpdatePositionOfVertices(Vector3 mousePosition, Vector3 direction) // Update position of all vertices
        {
            for (int i = 0; i < vertices.Count; i++)
            {
                Vector3 scp = transform.rotation * vertices[i].currentPos;
                Vector3 vertexPoint = new Vector3(scp.x * transform.localScale.x, scp.y * transform.localScale.y, scp.z * transform.localScale.z) + transform.position;
                var dis = Vector2.Distance(mousePosition, vertexPoint);
                float force;
                if (dis <= sizeImpact)
                {
                    force = Mathf.Clamp((Mathf.Abs((dis - sizeImpact) / sizeImpact) - 1.0f + 2 * forceImpact), 0, 1);
                    vertices[i].currentPos = vertices[i].currentPos + direction * force;
                }
                else
                {
                    if (_isSelfRestoring)
                    {
                        dis = Vector2.Distance(vertices[i].currentPos, vertices[i].initialPos);
                        var dis2 = Vector2.Distance(mousePosition, vertices[i].initialPos);
                        force = Mathf.Clamp(dis * dis2, 0, stiffnes * 1);
                        vertices[i].currentPos = vertices[i].currentPos + (vertices[i].initialPos - vertices[i].currentPos) * force;
                    }
                }
            }
        }

        public void UpdateRevertedPositionsOfVertices() {       // Update reverted position of vertices

            foreach (var item in vertices)
            {
                item.revertingPos = item.currentPos;
            }
            revert = 1;
        }

        public void Regenerate()                                        // Regenerates mesh
        {
            if (_isCustomShape)
            {
                AssignMesh(new CustomMesh(_sprite, refine));
            }
            else
            {
                AssignMesh(new RegularMesh(_sprite));
            }
            GenerateVerteciesList();
            ExtractMeshLines();
        }

        public void Reset()                                             // Resets mesh
        {
            foreach (var item in vertices)
            {
                item.currentPos = item.initialPos;
            }
            UpdateRevertedPositionsOfVertices();
            UpdateMesh();
        }

        private void UpdateMeshFromEditor()                              // Update mesh when not in play mode
        {                          
            if (Application.isEditor)
            {
                UpdateMesh();
            }
        }

        public void Revert(float slider)                                // Reverts position of meshes
        {
            foreach (var item in vertices)
            {
                item.currentPos = Vector3.Lerp(item.initialPos, item.revertingPos, slider);
            }
            UpdateMeshFromEditor();
        }

        public int GetIndexAtPosition(Vector3 pos) {

            int index = 0;
            Vector3 scp = transform.rotation * vertices[0].currentPos;
            Vector3 point = new Vector3(scp.x * transform.localScale.x, scp.y * transform.localScale.y, scp.z * transform.localScale.z) + transform.position;

            float minDistance = Vector2.Distance(pos, point);

            for (int i = 0; i < vertices.Count; i++)
            {
                scp = transform.rotation * vertices[i].currentPos;
                point = new Vector3(scp.x * transform.localScale.x, scp.y * transform.localScale.y, scp.z * transform.localScale.z) + transform.position;
                var dis = Vector2.Distance(pos, point);
                if (dis < minDistance)
                {
                    index = i;
                    minDistance = dis;
                }
            }
            //Debug.Log(index);
            return index;
        }

        public void SetSortingOrder(int sortingOrdr)
        {
           GetComponent<MeshRenderer>().sortingOrder = sortingOrdr;
           sortingOrder = sortingOrdr;
        }

        public void SetSortingLayerID(int newId)
        {
            GetComponent<MeshRenderer>().sortingLayerID = newId;
            sortingLayerID = newId;
        }

        public void SetNewColor(Color col) {
            GetComponent<MeshRenderer>().material.color = col;
        }
    }
}

The actual part of Update I want to translate to ECS is:

foreach (var vertex in vertices)
            {
                if (objectManagerController.metaballEdgePointVertices.Count > 0)
                {
                    // Assume FindNearestMagnetPoint() is now a public method in Vertex
                    Vector3 nearestMagnetPoint = vertex.FindNearestMagnetPoint();
                    //Debug.Log("Current vertex position: ");
                    // Debug.Log(vertex.currentPos);
                    // Debug.Log("Found nearest magnet point for current mesh vertex!");
                    // Debug.Log(nearestMagnetPoint);
                    // Update the vertex position
                    //vertex.UpdateCurrentVertex(nearestMagnetPoint, vertex._index);
                }
            }

of

private void Update()
        {
            UpdateMesh();
            foreach (var vertex in vertices)
            {
                if (objectManagerController.metaballEdgePointVertices.Count > 0)
                {
                    // Assume FindNearestMagnetPoint() is now a public method in Vertex
                    Vector3 nearestMagnetPoint = vertex.FindNearestMagnetPoint();
                    //Debug.Log("Current vertex position: ");
                    // Debug.Log(vertex.currentPos);
                    // Debug.Log("Found nearest magnet point for current mesh vertex!");
                    // Debug.Log(nearestMagnetPoint);
                    // Update the vertex position
                    //vertex.UpdateCurrentVertex(nearestMagnetPoint, vertex._index);
                }
            }
        }
```
\$\endgroup\$

1 Answer 1

0
\$\begingroup\$

I was able to optimize it using ECS and jobs:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Unity.Entities;
using Unity.Jobs;
using Unity.Collections;
using HerminkasGams.SpriteSquash;
using System.Linq;
using UnityEditor;
using Unity.Burst;
using System.Runtime.Serialization;
using Unity.VisualScripting;

public partial class VertexUpdater : SystemBase
{
    public GameObject ObjectManager;
    public ObjectManagerController objectManagerController;
    [HideInInspector] public List<SpriteMeshModifier> spriteMeshModifiers = new List<SpriteMeshModifier>();
    // [HideInInspector] public List<Vertex> vertices = new List<Vertex>();  // List of vertices; each element of this variable is what I must make magnetic

    private struct VertexCopy {
        public Vector3 currentPos;
        public Vector3 initialPos;
        public Vector3 revertingPos;
        public int _metaballEdgePointCount;

        //_parentSpriteMeshModifier variables:
        public Vector3 _parentSpriteMeshModifierPosition;
        public Quaternion _parentSpriteMeshModifierRotation;
        public Vector3 _parentSpriteMeshModifierLocalscale;

        [HideInInspector]public int _index;
        [HideInInspector]public Vector3 _closestMagnetPoint;
    
        public VertexCopy(Vector3 pos, int index, int metaballEdgePointCount, Vector3 parentSpriteMeshModifierPosition, Quaternion parentSpriteMeshModifierRotation, Vector3 parentSpriteMeshModifierLocalscale, Vector3 closestMagnetPoint) {
            currentPos = pos;
            initialPos = pos;
            revertingPos = pos;
            _index = index;
            _metaballEdgePointCount = metaballEdgePointCount;
            //_parentSpriteMeshModifierPosition variables
            _parentSpriteMeshModifierPosition = parentSpriteMeshModifierPosition;
            _parentSpriteMeshModifierRotation = parentSpriteMeshModifierRotation;
            _parentSpriteMeshModifierLocalscale = parentSpriteMeshModifierLocalscale;
            _closestMagnetPoint = closestMagnetPoint;
        }

        public (Vector3, float) FindNearestMagnetPoint(NativeArray<Vector3> metaballVertexPositionArray) {
            float minDistanceUnaltered = 99999;
            //Compute the nearest metaball edge magnet point by iterating through them if metaballEdgePointVertexGOs is not empty
            if (_metaballEdgePointCount > 0) {
                int index = 0;
                Vector3 scp = _parentSpriteMeshModifierRotation * currentPos;
                Vector3 point = new Vector3(scp.x * _parentSpriteMeshModifierLocalscale.x, scp.y * _parentSpriteMeshModifierLocalscale.y, scp.z * _parentSpriteMeshModifierLocalscale.z) + _parentSpriteMeshModifierPosition;
                
                float minDistance = 99999;

                //Will iterate through metaballEdgePointVertexGOs to find closest
                //Vector3 scpMagnet = new Vector3(0,0,0);
                for (int i = 0; i < _metaballEdgePointCount; i++)
                {   
                    var dis = Vector2.Distance(point, metaballVertexPositionArray[i]);
                    if (dis < minDistance)
                    {
                        index = i;
                        minDistance = dis;
                    }
                }
                
                //Invert metaballVertexPositionArray[index] back to vertex coordinates
                Vector3 vec1 = metaballVertexPositionArray[index] - _parentSpriteMeshModifierPosition;
                Vector3 vec2 = _parentSpriteMeshModifierLocalscale;
                Vector3 result = new Vector3(vec1.x / vec2.x, vec1.y / vec2.y, vec1.z / vec2.z);
                Vector3 scpLocal = Quaternion.Inverse(_parentSpriteMeshModifierRotation) * (result);
                // Debug.Log(currentPos);
                return (scpLocal, minDistance);
            }
            return (new Vector3(0,0,-1), minDistanceUnaltered);
        }
    }

    [BurstCompile]
    private struct ComputeJob : IJobFor {
        [NativeDisableParallelForRestriction]
        public NativeArray<VertexCopy> vertexCopyArray;
        [ReadOnly]
        public NativeArray<Vector3> metaballVertexPositionArray;
        
        public void Execute(int index) {
            VertexCopy vertexCopy = this.vertexCopyArray[index];
            float minDistance = 9999;
            (vertexCopy._closestMagnetPoint, minDistance) = vertexCopy.FindNearestMagnetPoint(metaballVertexPositionArray);
            //If closest distance is less than threshold
            if (minDistance < 0.2) {
                vertexCopy.currentPos = vertexCopy._closestMagnetPoint;
                this.vertexCopyArray[index] = vertexCopy;
            }
        }
    }

    protected override void OnStartRunning()
    {
        ObjectManager = GameObject.Find("ObjectManager");
        objectManagerController = ObjectManager.GetComponent<ObjectManagerController>();
        // GameObject.Find("SpriteMeshModifier");
        // EntityManager entityManager = World.DefaultGameObjectInjectionWorld.EntityManager;
        // // Create an Entity "archetype" that will have MyComponent
        // EntityArchetype spriteMeshModifierArchetype = entityManager.CreateArchetype(typeof(SpriteMeshModifier));
        // // Create an entity with that archetype
        // Entity spriteMeshModifierEntity = entityManager.CreateEntity(spriteMeshModifierArchetype);

        List<GameObject> waterslimesDisplay = objectManagerController.colorSlimesDisplayGlobal;
        foreach (GameObject waterslimeDisplay in waterslimesDisplay) {
            spriteMeshModifiers.Add(waterslimeDisplay.GetComponent<SpriteMeshModifier>());
        }
        base.OnStartRunning();
    }

    protected override void OnUpdate()
    {
        CallJobs();
        //throw new System.NotImplementedException();
    }

    private void CallJobs() {
        foreach (SpriteMeshModifier spriteMeshModifier in spriteMeshModifiers) {
            if(spriteMeshModifier.vertices.Count > 0 && spriteMeshModifier._isRegenerated && spriteMeshModifier.objectManagerController.metaballEdgePointVertices.Count > 0) {
                NativeArray<VertexCopy> vertexCopies = new NativeArray<VertexCopy>(spriteMeshModifier.vertices.Count, Allocator.TempJob);
                NativeArray<Vector3> metaballVertexPositions = new NativeArray<Vector3>(spriteMeshModifier.objectManagerController.metaballEdgePointVertices.Count, Allocator.TempJob);
                CopyThingsTo(ref vertexCopies, ref metaballVertexPositions, spriteMeshModifier);
                // Do heavy computation in a job
                ComputeJob job = new ComputeJob() {
                    vertexCopyArray = vertexCopies,
                    metaballVertexPositionArray = metaballVertexPositions
                };
                job.ScheduleParallel(vertexCopies.Length, 64, default).Complete();
                AssignComputedValues(vertexCopies, spriteMeshModifier);
                UpdatePositions(vertexCopies, spriteMeshModifier);
                // Don't forget to dispose
                vertexCopies.Dispose();
                metaballVertexPositions.Dispose();
            }
        }
    }

    private void CopyThingsTo(ref NativeArray<VertexCopy> copiesArray, ref NativeArray<Vector3> metaballVertexPositionsArray, SpriteMeshModifier spriteMeshModifier) {
        for (int i = 0; i < spriteMeshModifier.vertices.Count; i++) {
            Vertex vertex = spriteMeshModifier.vertices[i];
            copiesArray[i] = new VertexCopy(vertex.currentPos, i, vertex._objectManagerController.metaballEdgePointVertices.Count, vertex._parentSpriteMeshModifier.transform.parent.gameObject.transform.position, vertex._parentSpriteMeshModifier.gameObject.transform.rotation, vertex._parentSpriteMeshModifier.gameObject.transform.localScale, vertex._closestMagnetPoint);
        }
        for (int i = 0; i < spriteMeshModifier.objectManagerController.metaballEdgePointVertices.Count; i++) {
            MetaballEdgePointVertex metaballEdgePointVertex = spriteMeshModifier.objectManagerController.metaballEdgePointVertices[i];
            metaballVertexPositionsArray[i] = metaballEdgePointVertex.currentPos;
        }
    }

    private void AssignComputedValues(in NativeArray<VertexCopy> copiesArray, SpriteMeshModifier spriteMeshModifier) {
        for (int i = 0; i < spriteMeshModifier.vertices.Count; ++i) {
            spriteMeshModifier.vertices[i]._closestMagnetPoint = copiesArray[i]._closestMagnetPoint;
        }
    }

    private void UpdatePositions(in NativeArray<VertexCopy> copiesArray, SpriteMeshModifier spriteMeshModifier) {
        for (int i = 0; i < spriteMeshModifier.vertices.Count; ++i) {
            if(copiesArray[i].currentPos == copiesArray[i]._closestMagnetPoint) {
                //Should I lerp between copiesArray[i]._closestMagnetPoint and copiesArray[i].initialPos?
                spriteMeshModifier.vertices[i].UpdateCurrentVertex(copiesArray[i]._closestMagnetPoint, copiesArray[i]._index);
            }
            else {
                spriteMeshModifier.ResetPositionOfSelectedVertex(copiesArray[i]._index);
                spriteMeshModifier.UpdateMesh();
            }
        }
    }
}
```
\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .