private void UpdateTextAnimation() { dialogueBoxLabel.ForceMeshUpdate(); TMP_TextInfo textInfo = dialogueBoxLabel.textInfo; Vector3[] vertices = dialogueBoxLabel.mesh.vertices; Matrix4x4 matrix; Color32[] vertexColors; bool areAllCharactersFullAlpha = true; for (int i = 0; i < dialogueBoxLabel.textInfo.characterCount; i++) { TMP_CharacterInfo charInfo = textInfo.characterInfo[i]; // Skip this character if it is not visible if (!charInfo.isVisible) { continue; } int vertexIndex = charInfo.vertexIndex; int materialIndex = charInfo.materialReferenceIndex; vertexColors = textInfo.meshInfo[materialIndex].colors32; // Calculate the current alpha for each letter depending on whether it is fading/faded in or out float alpha = 0f; if (showText) { if (typewriterInProgress && textAnimationData != null) { TextAnimationData animationData = textAnimationData[i]; float animationTime = Time.unscaledTime - typewriterStartTime - animationData.fadeInDelay; alpha = Mathf.Clamp01(animationTime / animationData.fadeInDuration); } else { alpha = 1f; } } else { float animationTime = Time.unscaledTime - fadeOutStartTime; alpha = 1f - Mathf.Clamp01(animationTime / fadeOutDuration); } // Keep track of whether all characters are fully visible for the sake of knowing when // the typewriter animation has finished. if (areAllCharactersFullAlpha && !Mathf.Approximately(alpha, 1f)) { areAllCharactersFullAlpha = false; } // Set the character's alpha Color32 color = charInfo.color; color.a = (byte)(255 * alpha); vertexColors[vertexIndex + 0] = color; vertexColors[vertexIndex + 1] = color; vertexColors[vertexIndex + 2] = color; vertexColors[vertexIndex + 3] = color; // Animate vertex positions if (textAnimationData != null && textAnimationData[i].animationStyle != null) { TextAnimationData animationData = textAnimationData[i]; float typewriterTime = Time.unscaledTime - typewriterStartTime - animationData.fadeInDelay; DialogueManager.TextAnimationStyle style = animationData.animationStyle; // We want character animations to pivot at the character's center, so offset // the vertices before we do any transformations Vector3 offset = (charInfo.topLeft + charInfo.bottomRight) / 2; vertices[vertexIndex + 0] -= offset; vertices[vertexIndex + 1] -= offset; vertices[vertexIndex + 2] -= offset; vertices[vertexIndex + 3] -= offset; // Calculate the animated translation, rotation and scale of the character Vector3 translation = Vector3.zero; if (style.translation.type != DialogueManager.TextAnimationStyle.Type.None) { float animationFactor = Mathf.Sin((Time.time - style.translation.delayPerCharacter * i) * Mathf.PI / style.translation.duration); if (style.translation.type == DialogueManager.TextAnimationStyle.Type.Bounce) { animationFactor = Mathf.Abs(animationFactor); } translation = style.translation.offset * animationFactor; } if (style.translateIn.x.length > 0 && typewriterTime > 0f && typewriterTime <= style.translateIn.x.keys[style.translateIn.x.length - 1].time) { translation.x += style.translateIn.x.Evaluate(typewriterTime); } if (style.translateIn.y.length > 0 && typewriterTime > 0f && typewriterTime <= style.translateIn.y.keys[style.translateIn.y.length - 1].time) { translation.y += style.translateIn.y.Evaluate(typewriterTime); } Quaternion rotation = Quaternion.identity; float angle = 0f; if (style.rotation.type != DialogueManager.TextAnimationStyle.Type.None) { float animationFactor = Mathf.Sin((Time.time - style.rotation.delayPerCharacter * i) * Mathf.PI / style.rotation.duration); if (style.rotation.type == DialogueManager.TextAnimationStyle.Type.Bounce) { animationFactor = Mathf.Abs(animationFactor); } angle = style.rotation.startAngle + style.rotation.angleOffset * animationFactor; } if (style.rotateIn.length > 0 && typewriterTime > 0f && typewriterTime <= style.rotateIn.keys[style.rotateIn.length - 1].time) { angle += style.rotateIn.Evaluate(typewriterTime); } rotation = Quaternion.Euler(0f, 0f, angle); Vector3 scale = Vector3.one; if (style.scale.type != DialogueManager.TextAnimationStyle.Type.None) { float animationFactor = Mathf.Sin((Time.time - style.scale.delayPerCharacter * i) * Mathf.PI / style.scale.duration); if (style.scale.type == DialogueManager.TextAnimationStyle.Type.Bounce) { animationFactor = Mathf.Abs(animationFactor); } scale = style.scale.startScale + style.scale.scaleOffset * animationFactor; scale.z = 1f; } if (style.scaleIn.x.length > 0 && typewriterTime > 0f && typewriterTime <= style.scaleIn.x.keys[style.scaleIn.x.length - 1].time) { scale.x *= style.scaleIn.x.Evaluate(typewriterTime); } if (style.scaleIn.y.length > 0 && typewriterTime > 0f && typewriterTime <= style.scaleIn.y.keys[style.scaleIn.y.length - 1].time) { scale.y *= style.scaleIn.y.Evaluate(typewriterTime); } // Apply the calculated transformations matrix = Matrix4x4.TRS(translation, rotation, scale); vertices[vertexIndex + 0] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 0]); vertices[vertexIndex + 1] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 1]); vertices[vertexIndex + 2] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 2]); vertices[vertexIndex + 3] = matrix.MultiplyPoint3x4(vertices[vertexIndex + 3]); // Undo the offset to put the character back in the right place vertices[vertexIndex + 0] += offset; vertices[vertexIndex + 1] += offset; vertices[vertexIndex + 2] += offset; vertices[vertexIndex + 3] += offset; } } // Mark the typewriter animation as over once all characters are fully visible if (typewriterInProgress && areAllCharactersFullAlpha) { typewriterInProgress = false; } // Update the mesh dialogueBoxLabel.mesh.vertices = vertices; dialogueBoxLabel.UpdateVertexData(TMP_VertexDataUpdateFlags.Colors32); }
private void InitCharacterAnimationDataFromText(string sourceText) { textAnimationData = new List <TextAnimationData>(); // Define animation properties shared between multiple characters float totalFadeInDelay = 0f; float delayMultiplier = 1f; float fadeInDuration = typewriterFadeInDuration; bool readingRichTextMarkup = false; DialogueManager.TextAnimationStyle animationStyle = null; for (int sourceIndex = 0; sourceIndex < sourceText.Length; sourceIndex++) { // Define one-time animation properties for this character float pauseDuration = 0f; // Process character animation markup while (sourceText[sourceIndex] == '{') { // Read the command from the source text TextAnimationCommand command = new TextAnimationCommand(sourceText, sourceIndex); // Skip the source index ahead to the end of the command if (command.fullText.Length > 0) { sourceIndex += command.fullText.Length; } else { break; } // Process the command: switch (command.command.ToLower()) { // Add a one-time pause between characters with the the indicated duration. case "pause": { bool success = (command.args.Length > 0 && float.TryParse(command.args[0], out pauseDuration)); if (!success) { Debug.Log("Invalid argument(s) for command 'Pause'."); } } break; // Multiply the delay duration between characters until the end of the line (or until this command is called again). case "multiplydelay": { bool success = (command.args.Length > 0 && float.TryParse(command.args[0], out delayMultiplier)); if (!success) { Debug.Log("Invalid argument for command 'MultiplyDelay'."); } } break; // Multiply the fade-in duration for each character until the end of the line (or until this command is called again). case "fadeintime": { bool success = (command.args.Length > 0 && float.TryParse(command.args[0], out fadeInDuration)); if (!success) { Debug.Log("Invalid argument for command 'FadeInTime'."); } } break; // Set the animation style for this character and following characters case "animationstyle": { animationStyle = null; if (command.args.Length > 0) { for (int styleIndex = 0; styleIndex < DialogueManager.Instance.textAnimationStyles.Length; styleIndex++) { if (DialogueManager.Instance.textAnimationStyles[styleIndex].name == command.args[0]) { animationStyle = DialogueManager.Instance.textAnimationStyles[styleIndex]; break; } } } if (animationStyle == null) { Debug.LogWarningFormat("Invalid text animation style '{0}'.", command.args[0]); } } break; // Clear the animation style for this character and following characters case "clearanimationstyle": { animationStyle = null; } break; default: { Debug.LogWarningFormat("Unknown command '{0}'.", command.command); } break; } } // Skip this character if it is rich text markup if (sourceText[sourceIndex] == '<') { readingRichTextMarkup = true; } else if (sourceText[sourceIndex] == '>') { readingRichTextMarkup = false; continue; } if (readingRichTextMarkup) { continue; } // Calculate the delay duration for this character (time between the full text's // animation beginning and this character appearing). totalFadeInDelay += pauseDuration > 0f ? pauseDuration : typewriterCharacterDelay * delayMultiplier; // Create the animation data for this character TextAnimationData characterData = new TextAnimationData(); characterData.animationStyle = animationStyle; characterData.fadeInDelay = totalFadeInDelay; characterData.fadeInDuration = fadeInDuration; textAnimationData.Add(characterData); } }