Unity3d - How to make method calling from another

2019-07-11 21:22发布

This is the continuity from this question.

What I want to do here is to create a program to calculate some score based on the 3D model's movement and show it as a change of model's color.

But since the model's movement recorder, score calculation, and coloring are from different classes attached on different Game Object, I need to make them connect to each other to work together.

I come up with the solution like below snippet, but the system got laggy and freezing. I am new to Unity world, so I ask you guys, is there any method more efficient to do this kind of job?

Here is my code structure in detail, so this problem is involving 3 different class calling each other (all attached to different game object)

1) BoneHighlighter.cs

to do some re-coloring on the model based on script from previous question

//declare SkinnedMeshRenderer
public SkinnedMeshRenderer smr;
//initialization
//previously put the initialization on Start(), but I try to put it on Awake() to make the initialization a bit sooner
void Awake () 
{
    if (smr == null) smr = GetComponent<SkinnedMeshRenderer>();
    smr.sharedMesh = (Mesh)Instantiate(smr.sharedMesh);
}


// Change vertex colors highlighting given bone
public void Highlight(int index,double ratio = 1 ) 
{
    Transform[] bones = null;
    switch (index) 
    {
    case (int)Skeleton.Head:          bones = head;          break;
    case (int)Skeleton.UpperBody:     bones = upperBody;     break;
    case (int)Skeleton.LowerBody:     bones = lowerBody;     break;
    case (int)Skeleton.RightUpperArm: bones = upperArmRight; break;
    case (int)Skeleton.RightLowerArm: bones = lowerArmRight; break;
    case (int)Skeleton.RightHand:     bones = handRight;     break;
    case (int)Skeleton.LeftUpperArm:  bones = upperArmLeft;  break;
    case (int)Skeleton.LeftLowerArm:  bones = lowerArmLeft;  break;
    case (int)Skeleton.LeftHand:      bones = handLeft;      break;
    case (int)Skeleton.RightUpperLeg: bones = upperLegRight; break;
    case (int)Skeleton.RightLowerLeg: bones = lowerLegRight; break;
    case (int)Skeleton.RightFoot:     bones = footRight;     break;
    case (int)Skeleton.LeftUpperLeg:  bones = upperLegLeft;  break;
    case (int)Skeleton.LeftLowerLeg:  bones = lowerLegLeft;  break;
    case (int)Skeleton.LeftFoot:      bones = footLeft;      break;
    default: break;
    }

    //Debug.Assert(smr != null);
    if (smr != null) 
    {
        var mesh    = smr.sharedMesh;
        var weights = mesh.boneWeights;
        var colors  = new Color32[weights.Length];
        var sums    = new float[weights.Length];

        for (int j= 0; j<bones.Length; j++) 
        {
            var idx = GetBoneIndex (bones [j]);

            for (int i = 0; i < colors.Length; ++i) 
            {
                float sum = 0;
                if (weights [i].boneIndex0 == idx && weights [i].weight0 > 0)
                    sum += weights [i].weight0;
                if (weights [i].boneIndex1 == idx && weights [i].weight1 > 0)
                    sum += weights [i].weight1;
                if (weights [i].boneIndex2 == idx && weights [i].weight2 > 0)
                    sum += weights [i].weight2;
                if (weights [i].boneIndex3 == idx && weights [i].weight3 > 0)
                    sum += weights [i].weight3;
                sums [i] += sum;
                colors [i] = Color32.Lerp (regularColor, highlightColor, sums [i] * (float)ratio);
                mesh.colors32 = colors;
            }

            //Debug.Log("bone index:\t"+bones[j].ToString());
        }
    } 
    else Debug.Log("smr null");
}

2) Comparator.cs

This is where I call Highlight() function, this class will return float number 0-1 to determine the intensity of the color. This is how I call the Highlight() function

//declare highlighter class as public variable
//drag & drop GameObject containing BoneHighlighter.cs from property inspector
public BoneHighlighter highlighter = null;

//calculate a score and pass it to highlight() function
private void calculateScore(int data)
{
    .
    .
    highlighter.Highlight(highlightedRegion, cost);
}
//get the data from other game object
public void GetData(Frame frame) 
{
    calculateScore((int)Skeleton.RightHand);
}

3) Manager.cs

This class is used to get a 3D model data each frame and pass it to Comparator.cs for score calculation

public Comparator comparatorClass = null;
void Update () 
{
    .
    .
    comparatorClass.GetData(frame);
}

标签: c# unity3d 3d
2条回答
家丑人穷心不美
2楼-- · 2019-07-11 21:32

1) First problem is with Highlight implementation. The line mesh.colors32 = colors; should not be inside for. This way you assign mesh.colors32 multiple times, but what matters is only the last time you assign it, all others are overwritten. Should be:

 for (int j= 0; j<bones.Length; j++) {

    var idx = GetBoneIndex (bones [j]);

    for (int i = 0; i < colors.Length; ++i) {
        float sum = 0;
        if (weights [i].boneIndex0 == idx && weights [i].weight0 > 0)
            sum += weights [i].weight0;
        if (weights [i].boneIndex1 == idx && weights [i].weight1 > 0)
            sum += weights [i].weight1;
        if (weights [i].boneIndex2 == idx && weights [i].weight2 > 0)
            sum += weights [i].weight2;
        if (weights [i].boneIndex3 == idx && weights [i].weight3 > 0)
            sum += weights [i].weight3;
        sums [i] += sum;
        colors [i] = Color32.Lerp (regularColor, highlightColor, sums [i] * (float)ratio);
    }
}

mesh.colors32 = colors; // Finally, do it once

Making it a coroutine as it was suggested here is not needed, (and actually will make things worse).

2) Your PRIMARY PROBLEM is that you call Highligh on each Update. Highlight is not a very light function since it might need to access objects inside GPU memory when setting mesh colors. Update is called on each frame. You don't have to do Highligh on every frame - once something was highlighted, it will remain highlighted until "unhighlighted". You might want to call Highlight, only if something actually changed.

A simple way to do it is to remember what was highlighted on previous frame, and then highlight only if something changed. For example, your highlighter object may look like this:

//declare SkinnedMeshRenderer
public SkinnedMeshRenderer smr;
//initialization
//previously put the initialization on Start(), but I try to put it on Awake() to make the initialization a bit sooner
void Awake () 
{
    if (smr == null) smr = GetComponent<SkinnedMeshRenderer>();
    smr.sharedMesh = (Mesh)Instantiate(smr.sharedMesh);
}

// Remember the index and ratio used last time to highlight.
private int prevIndex = -1;
private int prevRatio = -1;

// Change vertex colors highlighting given bone
public void Highlight(int index,double ratio = 1 ) 
{
    // If nothing changed, no need to highligh again - everything is already
    // highlighted. Break function execution.
    if (index == prevIndex && ratio == prevRatio) return;

    Transform[] bones = null;
    switch (index) 
    {
    case (int)Skeleton.Head:          bones = head;          break;
    case (int)Skeleton.UpperBody:     bones = upperBody;     break;
    case (int)Skeleton.LowerBody:     bones = lowerBody;     break;
    case (int)Skeleton.RightUpperArm: bones = upperArmRight; break;
    case (int)Skeleton.RightLowerArm: bones = lowerArmRight; break;
    case (int)Skeleton.RightHand:     bones = handRight;     break;
    case (int)Skeleton.LeftUpperArm:  bones = upperArmLeft;  break;
    case (int)Skeleton.LeftLowerArm:  bones = lowerArmLeft;  break;
    case (int)Skeleton.LeftHand:      bones = handLeft;      break;
    case (int)Skeleton.RightUpperLeg: bones = upperLegRight; break;
    case (int)Skeleton.RightLowerLeg: bones = lowerLegRight; break;
    case (int)Skeleton.RightFoot:     bones = footRight;     break;
    case (int)Skeleton.LeftUpperLeg:  bones = upperLegLeft;  break;
    case (int)Skeleton.LeftLowerLeg:  bones = lowerLegLeft;  break;
    case (int)Skeleton.LeftFoot:      bones = footLeft;      break;
    default: break;
    }

    //Debug.Assert(smr != null);
    if (smr != null) 
    {
        var mesh    = smr.sharedMesh;
        var weights = mesh.boneWeights;
        var colors  = new Color32[weights.Length];
        var sums    = new float[weights.Length];

        for (int j= 0; j<bones.Length; j++) 
        {
            var idx = GetBoneIndex (bones [j]);

            for (int i = 0; i < colors.Length; ++i) 
            {
                float sum = 0;
                if (weights [i].boneIndex0 == idx && weights [i].weight0 > 0)
                    sum += weights [i].weight0;
                if (weights [i].boneIndex1 == idx && weights [i].weight1 > 0)
                    sum += weights [i].weight1;
                if (weights [i].boneIndex2 == idx && weights [i].weight2 > 0)
                    sum += weights [i].weight2;
                if (weights [i].boneIndex3 == idx && weights [i].weight3 > 0)
                    sum += weights [i].weight3;
                sums [i] += sum;
                colors [i] = Color32.Lerp (regularColor, highlightColor, sums [i] * (float)ratio);
            }

            //Debug.Log("bone index:\t"+bones[j].ToString());
        }

        mesh.colors32 = colors; // Finally, do it once
    } 
    else Debug.Log("smr null");
}
查看更多
闹够了就滚
3楼-- · 2019-07-11 21:38

1) Do you really have to call the comparatorClass.GetData(frame); on every update? if yes, then do you really need to call highlighter.Highlight(highlightedRegion, cost); on every update?

2) The other problem lies in the Highlight() method. You may want to move the following part:

 //Debug.Assert(smr != null);
if (smr != null) {
    var mesh = smr.sharedMesh;
    var weights = mesh.boneWeights;
    var colors = new Color32[weights.Length];
    var sums = new float[weights.Length];

    for (int j= 0; j<bones.Length; j++) {

        var idx = GetBoneIndex (bones [j]);

        for (int i = 0; i < colors.Length; ++i) {
            float sum = 0;
            if (weights [i].boneIndex0 == idx && weights [i].weight0 > 0)
                sum += weights [i].weight0;
            if (weights [i].boneIndex1 == idx && weights [i].weight1 > 0)
                sum += weights [i].weight1;
            if (weights [i].boneIndex2 == idx && weights [i].weight2 > 0)
                sum += weights [i].weight2;
            if (weights [i].boneIndex3 == idx && weights [i].weight3 > 0)
                sum += weights [i].weight3;
            sums [i] += sum;
            colors [i] = Color32.Lerp (regularColor, highlightColor, sums [i] * (float)ratio);
            mesh.colors32 = colors;
        }


        //Debug.Log("bone index:\t"+bones[j].ToString());
    }
} else {
    Debug.Log("smr null");
}

into a co-routine and start that co routine only once for specific case. fo eg:

public void Highlight(int index,double ratio = 1 ) {
StopAllCoroutines(); // stop all co routines already running.
Transform[] bones = null;
switch (index) {
case (int)Skeleton.Head:
    bones = head;
    break;
case (int)Skeleton.UpperBody:
    bones = upperBody;
    break;
case (int)Skeleton.LowerBody:
    bones = lowerBody;
default:
    break;
 if(bones != null)
 {       
   startCoroutine(actualHighlight(bones));
 }

}
Ienumrator actualHighlight(Transform[] bones)
{
 if (smr != null) {
    var mesh = smr.sharedMesh;
    var weights = mesh.boneWeights;
    var colors = new Color32[weights.Length];
    var sums = new float[weights.Length];

    for (int j= 0; j<bones.Length; j++) {

        var idx = GetBoneIndex (bones [j]);

        for (int i = 0; i < colors.Length; ++i) {
            float sum = 0;
            if (weights [i].boneIndex0 == idx && weights [i].weight0 > 0)
                sum += weights [i].weight0;
            if (weights [i].boneIndex1 == idx && weights [i].weight1 > 0)
                sum += weights [i].weight1;
            if (weights [i].boneIndex2 == idx && weights [i].weight2 > 0)
                sum += weights [i].weight2;
            if (weights [i].boneIndex3 == idx && weights [i].weight3 > 0)
                sum += weights [i].weight3;
            sums [i] += sum;
            colors [i] = Color32.Lerp (regularColor, highlightColor, sums [i] * (float)ratio);
            mesh.colors32 = colors;
            yield return new WaitForEndOfFrame();
        }


        //Debug.Log("bone index:\t"+bones[j].ToString());
    }
} else {
    Debug.Log("smr null");
 }
}

I hope this steers you into the right direction.

查看更多
登录 后发表回答