Vertex manipulation doesn't work with paging

        private IEnumerator DisplayCharacterByPage(Recitation recitation)
        {
            // Helper references
            TMP_TextInfo textInfo = textScript.textInfo;
            int characterCount = textInfo.characterCount;

            // Display no characters to start
            float appearTimeToWaitTimeRatio = 0.5f;
            textScript.text = recitation.GetLocalizedText();
            textScript.overflowMode = TextOverflowModes.Page;
            textScript.alignment = TextAlignmentOptions.Midline;
            // textScript.maxVisibleCharacters = 0;
            textScript.ForceMeshUpdate();

            // Store animation information
            AnimationInfo animInfo = new AnimationInfo(
                textScript.textInfo.characterCount, textScript, textInfo.CopyMeshInfoVertexData(), ClockIncrement);

            // Apply Emotion
            Coroutine[] emotionCoroutines = ApplyEmotion(animInfo);

            // Display characters
            float timePerPage = recitation.duration / textScript.textInfo.pageCount;
            int charsPerPage = textScript.textInfo.characterCount / textScript.textInfo.pageCount;
            int pagesWithExtra = textScript.textInfo.characterCount % textScript.textInfo.pageCount;

            // Calculate time to appear
            float appearTime = appearTimeToWaitTimeRatio * timePerPage;

            // Show characters
            int visibleCharacters = 0;
            for (int i = 1; i <= textScript.textInfo.pageCount; i++)
            {
                // Set visible characters
                textScript.pageToDisplay = i;

                // Show text
                int lastCharIndex = i == textScript.textInfo.pageCount ? textScript.textInfo.characterCount : textScript.textInfo.pageInfo[i - 1].lastCharacterIndex;
                int charsSoFar = textScript.textInfo.pageInfo[i - 1].firstCharacterIndex;
                int charsToAdd = lastCharIndex - charsSoFar;
                for (float j = 0; j <= appearTime; j += ClockIncrement)
                {
                    // Fix rounding errors
                    j = (float)System.Math.Round(j, ClockPrecisionDecimals);

                    // Set visible characters
                    float percentPageVisible = Mathf.Min(1f, j / appearTime);
                    int maxVisibleCharacters = charsSoFar + (int)Mathf.Ceil(charsToAdd * percentPageVisible);
                    // textScript.maxVisibleCharacters = maxVisibleCharacters;
                    AppearCharacters(ref visibleCharacters, maxVisibleCharacters, animInfo);

                    // Wait clock tick
                    yield return new WaitForSeconds(ClockIncrement);
                }

                // Wait a wait per page
                yield return new WaitForSeconds(timePerPage - appearTime);
            }
            textScript.text = "";

            // Stop with the emotions
            foreach (Coroutine coroutine in emotionCoroutines)
            {
                StopCoroutine(coroutine);
            }
        }

        private void AppearCharacters(ref int visibleCharacterIndex, int endIndex, AnimationInfo animInfo)
        {
            if (endIndex <= 0)
            {
                return;
            }
            TMP_TextInfo textInfo = textScript.textInfo;
            for (; visibleCharacterIndex < endIndex; visibleCharacterIndex++)
            {
                // Appear the invisible characters
                int vertexIndex = textInfo.characterInfo[visibleCharacterIndex].vertexIndex;
                int materialIndex = textInfo.characterInfo[visibleCharacterIndex].materialReferenceIndex;
                Color32[] newVertexColors = textInfo.meshInfo[materialIndex].colors32;
                Color32 color = newVertexColors[vertexIndex + 0];
                color.a = 255;
                newVertexColors[vertexIndex + 0] = color;
                newVertexColors[vertexIndex + 1] = color;
                newVertexColors[vertexIndex + 2] = color;
                newVertexColors[vertexIndex + 3] = color;

                // Signal visible
                animInfo.characterIsVisible[visibleCharacterIndex] = true;
            }
            textScript.UpdateVertexData(TMP_VertexDataUpdateFlags.Colors32);
        }

        private Coroutine[] ApplyEmotion(AnimationInfo animInfo)
        {
            TMP_TextInfo textInfo = textScript.textInfo;
            int linkCount = textScript.textInfo.linkCount;
            Coroutine[] coroutines = new Coroutine[linkCount];
            for (int i = 0; i < linkCount; i++)
            {
                int start = textScript.textInfo.linkInfo[i].linkTextfirstCharacterIndex;
                int end = start + textScript.textInfo.linkInfo[i].linkTextLength;
                RecitationTextEmotion emotion = (RecitationTextEmotion)Enum.Parse(typeof(RecitationTextEmotion), textScript.textInfo.linkInfo[i].GetLinkID());
                switch (emotion)
                {
                    case RecitationTextEmotion.Jitter:
                        JitterAnimationConfig jitterConfig = new JitterAnimationConfig();
                        jitterConfig.curveScale = 1.0f;
                        jitterConfig.angleMultiplier = 1.0f;
                        jitterConfig.speedMultiplier = 0.85f;
                        jitterConfig.startIndex = start;
                        jitterConfig.endIndex = end;
                        jitterConfig.animationInfo = animInfo;
                        coroutines[i] = StartCoroutine(SpeechAnimationRoutines.AnimateJitter(jitterConfig));
                        break;
                    case RecitationTextEmotion.SingSong:
                        SingSongAnimationConfig singSongConfig = new SingSongAnimationConfig();
                        singSongConfig.angle = 10.0f;
                        singSongConfig.angleIncrement = 2.5f;
                        singSongConfig.speedMultiplier = 1.0f;
                        singSongConfig.startIndex = start;
                        singSongConfig.endIndex = end;
                        singSongConfig.animationInfo = animInfo;
                        // coroutines[i] = StartCoroutine(SpeechAnimationRoutines.AnimateSingSong(singSongConfig));
                        break;
                    default:
                        throw new Exception("Unknown text emotion: " + emotion);
                }
            }
            return coroutines;
        }

        /// <summary>
        /// Animate a jitter in the letters.
        /// </summary>
        /// <param name="jitterConfig">Configuration information for the jitter.</param>
        /// <returns>IEnumerator for the coroutine.</returns>
        public static IEnumerator AnimateJitter(JitterAnimationConfig jitterConfig)
        {
            Matrix4x4 matrix;
            TMP_TextInfo textInfo = jitterConfig.animationInfo.textScript.textInfo;
            AnimationInfo animInfo = jitterConfig.animationInfo;
            float waitTime = animInfo.clockIncrement / jitterConfig.speedMultiplier;
            Debug.Log(textInfo.characterCount);
            while (true)
            {
                int i;
                for (i = jitterConfig.startIndex; i < jitterConfig.endIndex; i++)
                {
                    // Grab Character
                    TMP_CharacterInfo charInfo = textInfo.characterInfo[i];
                    if (charInfo.character == ' ')
                    {
                        continue;
                    }
                    if (!animInfo.characterIsVisible[i] || !charInfo.isVisible)
                    {
                        break;
                    }
                    int materialIndex = charInfo.materialReferenceIndex;
                    int vertexIndex = charInfo.vertexIndex;

                    // Get the cached vertices of the mesh used by this text element (character or sprite).
                    Vector3[] sourceVertices = animInfo.cachedMeshInfo[materialIndex].vertices;

                    // Determine the center point of each character.
                    Vector2 charMidBasline = (sourceVertices[vertexIndex + 0] + sourceVertices[vertexIndex + 2]) / 2;

                    // Need to translate all 4 vertices of each quad to aligned with middle of character / baseline.
                    // This is needed so the matrix TRS is applied at the origin for each character.
                    Vector3 offset = charMidBasline;
                    Vector3[] destinationVertices = textInfo.meshInfo[materialIndex].vertices;
                    destinationVertices[vertexIndex + 0] = sourceVertices[vertexIndex + 0] - offset;
                    destinationVertices[vertexIndex + 1] = sourceVertices[vertexIndex + 1] - offset;
                    destinationVertices[vertexIndex + 2] = sourceVertices[vertexIndex + 2] - offset;
                    destinationVertices[vertexIndex + 3] = sourceVertices[vertexIndex + 3] - offset;

                    // Apply Jitter
                    Vector3 jitterOffset = new Vector3(UnityEngine.Random.Range(-.25f, .25f), UnityEngine.Random.Range(-.25f, .25f), 0);
                    matrix = Matrix4x4.TRS(jitterOffset * jitterConfig.curveScale, Quaternion.Euler(0, 0, UnityEngine.Random.Range(-5f, 5f) * jitterConfig.angleMultiplier), Vector3.one);
                    destinationVertices[vertexIndex + 0] = matrix.MultiplyPoint3x4(destinationVertices[vertexIndex + 0]);
                    destinationVertices[vertexIndex + 1] = matrix.MultiplyPoint3x4(destinationVertices[vertexIndex + 1]);
                    destinationVertices[vertexIndex + 2] = matrix.MultiplyPoint3x4(destinationVertices[vertexIndex + 2]);
                    destinationVertices[vertexIndex + 3] = matrix.MultiplyPoint3x4(destinationVertices[vertexIndex + 3]);

                    // Translate all 4 vertices of each quad back to their original positions
                    destinationVertices[vertexIndex + 0] += offset;
                    destinationVertices[vertexIndex + 1] += offset;
                    destinationVertices[vertexIndex + 2] += offset;
                    destinationVertices[vertexIndex + 3] += offset;

                    // Push changes into meshes
                    textInfo.meshInfo[materialIndex].mesh.vertices = textInfo.meshInfo[materialIndex].vertices;
                    animInfo.textScript.UpdateGeometry(textInfo.meshInfo[materialIndex].mesh, materialIndex);
                }
                yield return new WaitForSeconds(waitTime);
            }
        }

I had another post that was very similar. I spent all day trying to figure out the problem and came to the conclusion that vertex manipulation doesn’t seem to function properly with paging.

The above example starts with DisplayCharacterByPage. When that routine is started, it begins changing alpha from 0 to 255 for each character, page by page. It also kicks off another coroutine that should find all link tags and apply the jitter effect to the characters in the link tags.

What i’ve found with this code is that page 1 works fine. Anything beyond page 1 and the letters that are animating jitter will disappear.

So I guess every time the page changes, you have to force a mesh update and replaced the cached mesh info. That was hard to figure out. It would be nice to have this documented somewhere. I just don’t have a firm understanding of how this stuff works, so it’d be nice to have somewhere to learn what’s going on under the hood.