Let’s do a mixed layout of graphics and text in Unity~

I haven't updated for a long time, and even my account has been lost. It took me a long time to get the password right.
I've been really busy recently, and even almost worked overtime last Sunday. Fortunately...
let's not talk nonsense, let's start! ! !

nonsense

First of all, this headline is all nonsense and can be skipped.

Mixed graphics and text, this is the most common function in a project. That's right, our project also has this requirement. Fortunately, I don't know how to do it! Otherwise, I wouldn't be writing this article.
Not only won't it, I even think this thing is troublesome and want to delay it as much as possible. However, they have arranged too much time for me, and I can't do it anymore.

So I started to Baidu to look at things written by others.
See the first one, make an atlas of the pictures you need, and then paste them with Mesh. Well, the limitations are very large, and it is beyond my comprehension at all. I don’t know if it can run normally when I quote it, and I have to print out an atlas. I don’t understand the performance, and I won’t change it if there are problems... Then
the The second is to make an Asset file, add the required pictures, then name it yourself, set the size... I personally think it is not as good as the first one, but it is used to make gif effects. But most projects don’t need such troublesome things, it’s better to just get a Skeleton plug-in in, and then apply the third method.
The last is the third method, which is to modify the OnPopulateMesh method of Text. In the process of obtaining vertex information, change the string of the picture into a space with a suitable width through regularization, and record the relevant data of the picture. Then mount several images on the object, and assign the image information into it.

The third way looks good, mainly because the code he gave is complete and the logic is easy to see. However, after fumbling with it, I found many problems, especially the wrong positions. I can only change it silently. After a long time, it finally looks almost the same.

Two months passed in such a peaceful way, and a list came [Under different resolutions, the position of mixed graphics and texts is wrong] It was
too uncomfortable, so I had to re-read it bit by bit, and started to break down to see each item. Vertex information. Finally solved a lot of problems, for example, the number of spaces occupied by the picture is wrong, the calculation of the vertex in the center of the picture position is wrong, the line break and the original space are not calculated, etc... Sure enough, people can't be lazy. There was no such thing as before
. I have completely seen through it, and now I am forced to sort out the logic completely. -_-

In this way, I thought it was finally over, but yesterday I made another list [On the mobile phone, occasionally there are only pictures but no text]
This made me very distressed, and I started various investigations, and finally I probably know , It should be the problem of OnPopulateMesh, but how should I change it...
Until the night, I was thinking about this problem before going to bed, and suddenly thought, I can override the set method of text, and replace the text with the final one when inputting result. In this way, OnPopulateMesh is triggered under the underlying logic, which should not be a problem.

So today, I came to the company, went to git to find the source code of UGUI2019, and looked at the logic of the text. Note out the overridden OnPopulateMesh method, and directly perform regular processing on m_Text in the set method of text. I tried it, but it works.
Just when I was happy, I found a new problem. Because it is processed in the set method, the value of m_Text will be completely overwritten. This means that if it was sitting directly in the prefab, the original value is completely lost.

emmmmm, I had to change it back. It seems that we can only look at what is wrong with the OnPopulateMesh method. I will continue to look at the way OnPopulateMesh is written in the source code. I found that there are many differences from the methods I found on the Internet.
I understand, because the method I copied is from 2017, and the current project is from 2019, so the underlying logic is different. This should be the problem.
But new worries come again, because the git is from 2019.1.05, and my project is from 2019.4, I can't make sure that the code is valid. And what if 2018 or 2020 or even 2025 is used later?
I had to reorganize the logic and found that text was called in the OnPopulateMesh method, that is to say, the core of OnPopulateMesh override is to obtain and modify the text to be correct. Then can I do calculation and modification in the get method of text.
Try it out, note the OnPopulateMesh method, and override the get method of text.
It succeeded, good guy, it turns out that only need to change the source code.

The nonsense is really over, let's start~

text

Here is the real code logic part

Let me talk about the logic of this approach first.
1. When text is entered, the two methods SetVerticesDirty and SetLayoutDirty will be triggered to notify the bottom layer that the current rendering information is dirty, and then OnPopulateMesh will be triggered actively.
2. Modify the get method of text, and re-correct the m_Text that should have been returned. But do not modify m_Text itself to prevent pollution of its own data.
3. The logic of the revision is to remove the part of <icon=XXX> with regular expressions first, and replace it with an appropriate number of spaces. Then enter the properties of the picture into the list.
4. Create a corresponding number of images, and modify the display through the properties of the images.

Then talk about the writing method of mixed text and graphics

This script can show <icon name=xxx size=40 x=0 y=0/> and string.

insert image description here

script private variables

public class MixedPicText : Text
{
    
    
    //空格的编码(只读)
    private static readonly string replaceStr = "\u00A0";
    //图片部分的正则表达式(只读)
    private static readonly Regex imageTagRegex = new Regex(@"<icon name=([^>\s]+)([^>]*)/>");//(名字)(属性)
    //图片属性的正则表达式(只读)
    private static readonly Regex imageParaRegex = new Regex(@"(\w+)=([^\s]+)");//(key)=(value)
    //图片属性类列表
    private List<RichTextImageInfo> imageInfoList = new List<RichTextImageInfo>();
    //是否图片有变化的Dirty
    private bool isImageDirty = false;
    //顶点信息,是在计算图片位置时需要的
    private IList<UIVertex> verts = null;
    //当前已经创建的图片
    private List<RectTransform> showImageList = new List<RectTransform>();
}

Image attribute class

public class MixedPicTextImageInfo
{
    
    
    public string name;       //名字(路径)
    public Vector2 position;  //位置
    public int startVertex;   //起始顶点
    public int vertexLength;  //占据顶点数

    //标签属性
    public int size = 40;    //尺寸
    public float offsetX = 0f;  //X偏移
    public float offsetY = 0f;  //Y偏移

    public void SetValue(string key, string value)
    {
    
    
        switch (key)
        {
    
    
            case "size":
                {
    
    
                    int.TryParse(value, out size);
                    break;
                }
            case "x":
                {
    
    
                    float.TryParse(value, out offsetX);
                    break;
                }
            case "y":
                {
    
    
                    float.TryParse(value, out offsetY);
                    break;
                }
            default:
                break;
        }
    }
}

So let's talk about how to disassemble

    protected string CalculateLayoutWithImage(string richText)
    {
    
    
    	//获取填充文本的生成设置
        Vector2 extents = rectTransform.rect.size;
        var settings = GetGenerationSettings(extents);
        //计算空格的宽度
        float unitsPerPixel = 1 / pixelsPerUnit;
        float spaceWidth = cachedTextGenerator.GetPreferredWidth(replaceStr, settings) * unitsPerPixel;

        //解析图片标签,并将标签替换为空格
        imageInfoList.Clear();
        Match match = null;
        //创建一个StringBuilder,用来存储替换后的字符串
        StringBuilder builder = new StringBuilder();
        while ((match = imageTagRegex.Match(richText)).Success)
        {
    
    
            //拆解正则数据
            MixedPicTextImageInfo imageInfo = new MixedPicTextImageInfo();
            imageInfo.name = match.Groups[1].Value;
            string paras = match.Groups[2].Value;
            if (!string.IsNullOrEmpty(paras))
            {
    
    
                //拆解图片的属性
                var keyValueCollection = imageParaRegex.Matches(paras);
                for (int i = 0; i < keyValueCollection.Count; i++)
                {
    
    
                    string key = keyValueCollection[i].Groups[1].Value;
                    string value = keyValueCollection[i].Groups[2].Value;
                    imageInfo.SetValue(key, value);
                }
            }
            imageInfo.startVertex = match.Index * 4;
            //占据几个空格 一般图片和文字间需要间距 所以多一个空格
            int num = Mathf.CeilToInt(imageInfo.size / spaceWidth) + 1;
            imageInfo.vertexLength = num * 4;
            imageInfoList.Add(imageInfo);
            
            //将字符串数据添加
            builder.Length = 0;
            builder.Append(richText, 0, match.Index);
            for (int i = 0; i < num; i++)
            {
    
    
                builder.Append(replaceStr);
            }
            builder.Append(richText, match.Index + match.Length, richText.Length - match.Index - match.Length);
            richText = builder.ToString();
        }

        //用新的文本来构成数据,获取新的顶点信息等
        cachedTextGenerator.Populate(richText, settings);
        verts = cachedTextGenerator.verts;
        int vertCount = verts.Count;

        //计算图片位置
        for (int i = imageInfoList.Count - 1; i >= 0; i--)
        {
    
    
            MixedPicTextImageInfo imageInfo = imageInfoList[i];

            int charIndex = imageInfo.startVertex / 4;
            string str = richText.Substring(0, charIndex);
            int newLine = str.Split('\n').Length - 1;
            int whiteSpace = str.Split(' ').Length - 1;
            int indexOfTextQuad = (charIndex * 4) - newLine * 4 - whiteSpace * 4;
            if (indexOfTextQuad < vertCount) {
    
    
                Vector2 pos = (verts[indexOfTextQuad].position +
                    verts[indexOfTextQuad + 1 + imageInfo.vertexLength - 4].position +
                    verts[indexOfTextQuad + 2 + imageInfo.vertexLength - 4].position +
                    verts[indexOfTextQuad + 3].position) / 4f;
                
                //计算位置的缩放,在Canvas的RenderMode为ScreenSpaceCamera的情况下,不同分辨率的位置修正
                float posScale = 1f;
#if UNITY_EDITOR
                if (Application.isPlaying)
                {
    
    
                	//todo UICanvas是我自己项目中的,就是获取canvas而已,根据自己需求来改
                    ---
                    posScale = UICanvas.Get().canvas.scaleFactor;
                    ---
                }
                else
                {
    
    
                    if (canvas != null)
                    {
    
    
                        posScale = canvas.scaleFactor;
                    }
                }
#else
                posScale = UICanvas.Get().canvas.scaleFactor;
#endif
                pos /= posScale;//适应不同分辨率的屏幕
                pos += new Vector2(imageInfo.offsetX + spaceWidth / 2, imageInfo.size * 0.3f + imageInfo.offsetY);

                imageInfo.position = pos;
            } else {
    
    
                imageInfoList.RemoveAt(i);
            }
        }

		//此时认为图片有变化
        isImageDirty = true;

        return richText;
    }

The method of correcting the string is ready, then the installation can begin.

    public override string text {
    
    
        get
        {
    
    
        	//覆写text的get方法,直接返回修正后的字符串。不要动m_Text,会导致数据修改且不可逆
            return CalculateLayoutWithImage(m_Text);
        }
        set => base.text = value;
    }

Next, let's talk about generating pictures. The original article is placed in Update, I don't know why, and I haven't tried to put it in other places, so I will keep it as it is.

    protected void Update()
    {
    
    
        if (isImageDirty)
        {
    
    
            isImageDirty = false;

			//用池的方法来创建图片
            RectTransform imgTrans;
            Image imageComp;
            for (int i = 0; i < imageInfoList.Count; i++)
            {
    
    
                if (i < showImageList.Count)
                {
    
    
                    imgTrans = showImageList[i];
                    imgTrans.gameObject.SetActive(true);
                    imageComp = imgTrans.GetComponent<Image>();
                }
                else
                {
    
    
                    imgTrans = new GameObject("Image", typeof(RectTransform)).transform as RectTransform;
                    imgTrans.SetParent(transform);
                    imageComp = imgTrans.gameObject.AddComponent<Image>();
                    showImageList.Add(imgTrans);
                }

                MixedPicTextImageInfo imageInfo = imageInfoList[i];
                imgTrans.localScale = Vector3.one;
                
                //todo 这里直接使用自己项目中的加载图片的方法
				---
				imageComp.sprite = sprite;
				---
                
                imageComp.SetNativeSize();
                imageComp.raycastTarget = false;
                imgTrans.localScale = Vector3.one * imageInfo.size / imgTrans.rect.width;
                imgTrans.anchoredPosition = imageInfo.position;
            }

            for (int i = imageInfoList.Count; i < showImageList.Count; i++)
            {
    
    
                showImageList[i].gameObject.SetActive(false);
            }
        }
    }
}

Finally, there are two overriding methods

	//Awake的时候会触发SetDirty,所以先将原有的数据都清理掉,防止污染
    protected override void Awake()
    {
    
    
        base.Awake();
        showImageList.Clear();
        for (int i = transform.childCount - 1; i >= 0; i--)
        {
    
    
#if UNITY_EDITOR
            if (Application.isPlaying)
            {
    
    
#endif
                Destroy(transform.GetChild(i).gameObject);
#if UNITY_EDITOR
            }
            else
            {
    
    
                DestroyImmediate(transform.GetChild(i).gameObject);
            }
#endif
        }
    }
	//这个方法是在编辑模式下的处理,不然打开预制体的时候,因为Awake的原因,图片被清理掉了。
    protected override void Start()
    {
    
    
        base.Start();
        OnPopulateMesh(new VertexHelper());
    }

epilogue

This was only modified yesterday. In fact, there will be other problems. It needs to be checked again.
If there are new problems, I will come back and change them.
insert image description here

Guess you like

Origin blog.csdn.net/qql7267/article/details/122223641