using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using AibisDream.Framework; using AibisDream.Utility; using UnityEngine; using Yarn.Unity; using Yarn.Unity.Attributes; namespace AibisDream { public class AibisLineProvider : LineProviderBehaviour { public override string LocaleCode { get => _textLocaleCode; set => _textLocaleCode = value; } [SerializeField, Language] private string _textLocaleCode = System.Globalization.CultureInfo.CurrentCulture.TwoLetterISOLanguageName; [SerializeField, Language] private string _assetLocaleCode = System.Globalization.CultureInfo.CurrentCulture.TwoLetterISOLanguageName; [SerializeField] private bool _useFallback = false; [ShowIf(nameof(_useFallback))] [SerializeField, Language] private string _fallbackLocaleCode = System.Globalization.CultureInfo.CurrentCulture.TwoLetterISOLanguageName; [SerializeField] private NarrativeTextStyleConfig _narrativeTextStyle; public string AssetLocaleCode { get => _assetLocaleCode; set => _assetLocaleCode = value; } private Yarn.Markup.LineParser lineParser = new Yarn.Markup.LineParser(); private Yarn.Markup.BuiltInMarkupReplacer builtInReplacer = new Yarn.Markup.BuiltInMarkupReplacer(); void Awake() { lineParser.RegisterMarkerProcessor("select", builtInReplacer); lineParser.RegisterMarkerProcessor("plural", builtInReplacer); lineParser.RegisterMarkerProcessor("ordinal", builtInReplacer); } public override async YarnTask GetLocalizedLineAsync(Yarn.Line line, CancellationToken cancellationToken) { // 每行对白即是消费点:在此同步一次当前语言,保证对话进行中切语言下一行立即生效 _textLocaleCode = LocalizationKit.CurrentLanguageCode; _assetLocaleCode = LocalizationKit.CurrentLanguageCode; string sourceLineID = line.ID; string[] metadata = System.Array.Empty(); if (YarnProject != null) { metadata = YarnProject.lineMetadata?.GetMetadata(line.ID) ?? System.Array.Empty(); var shadowLineSource = YarnProject.lineMetadata?.GetShadowLineSource(line.ID); if (shadowLineSource != null) { sourceLineID = shadowLineSource; } } string text = GetLocalizedString(sourceLineID); Object asset = await GetLocalizedAssetAsync(sourceLineID); if (text == null) { Debug.LogWarning($"Localization {LocaleCode} does not contain an entry for line {line.ID}", this); return LocalizedLine.InvalidLine; } // 对 substitutions 并行做本地化解析(保留旧 LocalisedLineProvider 的能力)。 if (line.Substitutions.Length > 0) { var localizationTasks = new Task[line.Substitutions.Length]; for (int i = 0; i < line.Substitutions.Length; i++) localizationTasks[i] = LocalizationKit.LocalizeParamAsync(line.Substitutions[i]); string[] localizedSubstitutions = await Task.WhenAll(localizationTasks); for (int i = 0; i < localizedSubstitutions.Length; i++) line.Substitutions[i] = localizedSubstitutions[i]; } // 先展开项目级语义标记,再保留旧 LocalisedLineProvider 的文本转义处理。 // ULM 的 [[vertexp ...]] 会由 Escape 转成 Text Animator 使用的花括号标签。 text = NarrativeTextStyleExpander.Expand(text, _narrativeTextStyle); text = CommonUtil.Escape(text); var parseResult = lineParser.ParseString(Yarn.Markup.LineParser.ExpandSubstitutions(text, line.Substitutions), LocaleCode); return new LocalizedLine { Text = parseResult, RawText = text, TextID = line.ID, Asset = asset, Metadata = metadata, }; } private Yarn.Unity.Localization GetLocalization(string locale) { if (YarnProject == null) { throw new System.InvalidOperationException("Can't get localized line: no Yarn Project set"); } Localization loc = YarnProject.GetLocalization(locale); if (loc == null) { throw new System.InvalidOperationException($"Can't get localized line: Yarn Project has no localisation for {locale}"); } return loc; } private string GetLocalizedString(string sourceLineID) { var baseLoc = GetLocalization(_textLocaleCode); string localizedText = baseLoc.GetLocalizedString(sourceLineID); if (localizedText != null) { return localizedText; } if (_useFallback) { var fallbackLoc = GetLocalization(_fallbackLocaleCode); return fallbackLoc.GetLocalizedString(sourceLineID); } return null; } private async YarnTask GetLocalizedAssetAsync(string sourceLineID) { var baseLoc = GetLocalization(_assetLocaleCode); Object result = await baseLoc.GetLocalizedObjectAsync(sourceLineID); if (result != null) { return result; } if (_useFallback) { var fallbackLoc = GetLocalization(_fallbackLocaleCode); return await fallbackLoc.GetLocalizedObjectAsync(sourceLineID); } return null; } public async override YarnTask PrepareForLinesAsync(IEnumerable lineIDs, CancellationToken cancellationToken) { if (YarnProject == null) { return; } var assetLocalization = YarnProject.GetLocalization(AssetLocaleCode); if (assetLocalization.UsesAddressableAssets) { var tasks = new List>(); foreach (var id in lineIDs) { var task = assetLocalization.GetLocalizedObjectAsync(id); tasks.Add(task); } await YarnTask.WhenAll(tasks); } else { return; } } public override void RegisterMarkerProcessor(string attributeName, Yarn.Markup.IAttributeMarkerProcessor markerProcessor) { lineParser.RegisterMarkerProcessor(attributeName, markerProcessor); } public override void DeregisterMarkerProcessor(string attributeName) { lineParser.DeregisterMarkerProcessor(attributeName); } } }