Persistent Variables Source
Smart String に引数を指定するためには、多くの場合はスクリプトの記述が必要になります。Persistent Variables Sourceは、Global Variable (グローバル変数) および LocalizedString Local Variable (ローカル変数) と連携して、スクリプトなしでの Smart String への引数指定を可能にします。エディター内で様々なデータ型の引数を設定し、カスタム引数を作成することができます。
Local Variables
Local Variable (ローカル変数) を使用すると、ローカライズ済み文字列に引数を設定することができます。この文字列はスクリプトで駆動できます。ローカル変数はエディターまたはスクリプトを使用して追加できます。使用中のローカル変数の値を変更すると、ローカライズ済み文字列が自動的に更新されます。
ローカル変数は再生モードの外でプレビューできるので、Smart String が素早く作成できます。このため、ローカル変数は Smart String を様々な入力でテストするのに最適です。ローカル変数は Smart String が完了したら置き換えることができます。
{player-name} scored {score} points and {objective:Completed|Failed} the objective
. という Smart String は、Player 1 scored 55 points and Completed the objective
になります。
グローバル変数
複数の Global Variable (グローバル変数) を使用して Persistent Variables ソースを設定することで、追加的な引数を用いることなくデータを外部のソースからプルして Smart String に変換することができます。グローバル変数の値が変更された時に、現在それを使用している LocalizedStrings が自動的に更新されるように設定することも可能です。
グローバル変数にアクセスするには、以下の構造を持つプレースホルダー内でドット表記を使用します。
{group-name.variable-name}
{group-name.nested-group-name.variable-name}
Variables Group アセット
Variables Group アセットは、Persistent Variable (永続変数) あるいは Nested Variable Group (ネストした変数グループ) を複数含むことができます。Variable Group の作成は Assets メニューから行えます (Assets > Create > Localization > Variables Group)。Variables Group は、Global Variables Group (Smart String からアクセス可能) として Persistent Variables ソースに割り当てることができます。また、ローカライズ済み文字列の Local Argument (ローカル引数) に割り当てることも可能です。ローカル引数は、そのローカライズ済み文字列インスタンスからのみアクセス可能です。
Smart String から Global Variables (グローバル変数) へのアクセスを可能にするには、Smart Format Sources リストに Persistent Variables Source を追加する必要があります。
スペースを含まない一意の名前を持つ Variable Group であれば、リストに追加できます。
以下は、global という名前の 1 つのグループの例です。
Smart String の例 | 結果 |
---|---|
My Floats value is {global.my-float} | My Floats value is 1.23 |
{global.my-string} of using global variables. | This is an example of using global variables. |
The door is {global.my-bool:Open|Closed}. | The door is Open |
This is an integer {global.int-variable} as Hex {global.int-variable:X}. | This is an integer 255 as Hex FF |
Values can be nested {global.nested-group.player-name} | Values can be nested Player 1 |
更新のトリガー
Persistent Variable は、現在それを使用している LocalizedStrings に自動更新をトリガーできます。つまり、更新が必要な UI フィールドがどれかをトラッキングできます。値が変更されると LocalizedStrings が自動的に新しい値に更新されます。LocalizedStrings を自動的に更新するように変数を設定するには、変数に IVariableValueChanged インターフェースを実装させます。LocalizedStrings は、それがどの変数を参照するか確認し、その変数の Value Changed イベントに自身を追加します。値が変更されると、Value Changed イベントが実行されて LocalizedStrings が自動的に新しい値に更新されます。
var source = LocalizationSettings.StringDatabase.SmartFormatter.GetSourceExtension<PersistentVariablesSource>();
var myFloat = source["global"]["my-float"] as FloatVariable;
// This will trigger an update
myFloat.Value = 123;
PersistentVariablesSource Update Scope 内で更新することで、複数の変数を変更した場合に LocalizedStrings の自動更新を停止することができます。これによって不要な更新を防ぐことができます。
public class RandomPlayerStats : MonoBehaviour
{
public string[] stats = new[] { "vitality", "endurance", "strength", "dexterity", "intelligence" };
public void RandomStats()
{
var source = LocalizationSettings.StringDatabase.SmartFormatter.GetSourceExtension<PersistentVariablesSource>();
var nestedGroup = source["global-sample"]["player"] as NestedVariablesGroup;
// An UpdateScope or using BeginUpdating and EndUpdating can be used to combine multiple changes into a single Update.
// This prevents unnecessary string refreshes when updating multiple Global Variables.
using (PersistentVariablesSource.UpdateScope())
{
foreach (var name in stats)
{
var variable = nestedGroup.Value[name] as IntVariable;
variable.Value = Random.Range(0, 10);
}
}
}
}
Persistent Variable の型
デフォルトでは以下の Persistent Variable (永続変数) が追加可能です。
名前 | 説明 |
---|---|
Float | 単一の float 値を含みます。 |
String | 単一の文字列値を含みます。 |
Integer | 単一の整数値を含みます。 |
Bool | 単一のブール値を含みます。 |
Object Reference | UnityEngine オブジェクトを参照するために使用できます。 |
Nested Variables Group | Persistent Variables Group アセットへの参照を含めるために使用できます。変数のネスティングも可能です。 |
LocalizedString | ネストされた翻訳を提供するために使用できます。 |
ネストされた翻訳
Localized String は永続変数にすることができ、これによって翻訳のネスティングが可能になります。
例えば、以下の動的文字列では、ローカライズされた値を項目名と色の両方に使用することもできます。
The {item} is {color}.
Localized String は以下のように設定する必要があります。
ネストされた Localized String は書式設定の段階で抽出されます。ネストされた Localized String が現在使用不可能な場合 (テーブルがまだ読み込み中の場合など) は、空の文字列が返されます。文字列が読み込まれると ValueChanged イベントがトリガーされ、これがルート LocalizedString の更新をトリガーします。これを回避したい場合は、(ネストされたテーブルがルートテーブルと異なる場合は) ネストされたテーブルの プリロード を有効にすることをお勧めします。
性別の処理
一部の言語では、使用すべき単語のバージョンが性別 (gender) によって変わることがあります。例えばスペイン語では、全ての名詞に性別があり、男性か女性のどちらかです。 項目の性は文章の構造に影響を及ぼします。
The {item} is {color}
という文章は、スペイン語に翻訳されると性に基づいて変化します。
masculine (男性) の場合、El {item} es {masculine color}
という構造になります。
feminine (女性) の場合、
La {item} es {feminine color}
という構造になります。
例えば、項目が pen (bolígrafo) (ペン) である場合は男性バージョンが使用される必要があり、色が赤 (red) であれば、それに関しても男性バージョン (rojo) を使用する必要があります。
文章は El bolígrafo es rojo
となります。
これを処理するひとつの方法は Choose フォーマッターを使用することです。
例えば {gender:choose(Male|Female):El|La} {item} es {color}
を例に挙げましょう。この例では、LocalizedString 内でローカル変数によって性を定義することができます。
翻訳者が項目の性を定義できるようにするには、例えば以下のようにメタデータを追加します。
[Metadata(AllowedTypes = MetadataType.StringTableEntry)]
[Serializable]
public class ItemGender : IMetadata
{
public enum Gender
{
None,
Female,
Male
}
public Gender gender = Gender.None;
}
これにより、翻訳者が Spanish Entry (スペイン語のエントリー) にメタデータを追加してそのエントリーの性別を定義できるようになります。これを Google スプレッドシート や CSV に同期させれば、翻訳者が Unity を使用せずにリモートで性別を設定できるようになります。
メタデータをアクセス可能にするには、メタデータスクリプト内に IMetadataVariable インターフェースを実装してください。
以下は、Gender メタデータの更新方法の一例です。
[Metadata(AllowedTypes = MetadataType.StringTableEntry)]
[Serializable]
public class ItemGender : IMetadata, IMetadataVariable
{
public enum Gender
{
None,
Female,
Male
}
public Gender gender = Gender.None;
/// <summary>
/// The name used to identify this metadata.
/// </summary>
public string VariableName => "gender";
public object GetSourceValue(ISelectorInfo _) => gender;
}
VariableName は、どのメタデータを使用するかを特定します。その上で GetSourceValue が書式設定中にその値を抽出します。
{item.gender} を含んだ文字列は、返されたエントリーから Gender メタデータを抽出できます。 ネストされた Localized String も現在の値にアクセスでき、これはネストされたスコープ内で使用できます。つまり、red の翻訳された文字列は以下のようになります。
{gender:choose(Male|Female):rojo|roja}
セレクター gender はまず現在の値でチェックされ、見付からなかった場合はローカル変数がチェックされます。つまり、完全なパスを指定する必要がなく、(gender に一致するセレクターを持つものであれば) 様々な型のデータを使用できます。
最終的な Spanish (スペイン語) の翻訳は以下のようになります。
{item.gender:choose(Male|Female):El|La} {item} es {item:{color}}
ネストされた翻訳の書式設定を行う場合、それらは現在の LocalizedString 内で定義された親ローカル変数とローカル変数にアクセスすることができます。ローカル変数が最初に評価され、ローカルで一致がなければ、親変数が続いて評価されます。
色は以下のように翻訳されます。
色 | Spanish (スペイン語) |
---|---|
red | {gender:choose(Male|Female):rojo|roja} |
green | verde |
blue | azul |
white | {gender:choose(Male|Female):blanco|blanca} |
black | {gender:choose(Male|Female):negro|negra} |
ノート: 性別バージョンがない色もあります。
以下の図は、この Smart String の構造を示したものです。
この図は、"The backpack is red" の Spanish (スペイン語) 翻訳がどのように解決されるかを示しています。
上の例は、パッケージサンプル内の Persistent Variables サンプル下にあります。
カスタムの永続変数
カスタム変数は、シリアル化可能 (Serializable) で IVariable を実装している必要があります。以下の例は、変数を使用して現在の時刻を返す方法を示しています。
/// <summary>
/// This is an example of a Global Variable that can return the current time.
/// </summary>
[DisplayName("Current Date Time")]
public class CurrentTime : IVariable
{
public object GetSourceValue(ISelectorInfo _) => DateTime.Now;
}
この例は、XML テキストを XElement 変数に変換するための変数の作成方法を示しています。
[DisplayName("XML Text")]
public class XmlElement : IVariable
{
public string xmlText;
public object GetSourceValue(ISelectorInfo selector)
{
try
{
if (!string.IsNullOrEmpty(xmlText))
{
return XElement.Parse(xmlText);
}
}
catch (Exception e)
{
Debug.LogException(e);
}
return null;
}
}
変数を使用する LocalizedString の更新をトリガーするには、IVariableValueChanged インターフェースを実装してください。
[Serializable]
public class MyVariable : IVariable, IVariableValueChanged
{
[SerializeField]
string m_Value;
public string Value
{
get => m_Value;
set
{
if (m_Value == value)
return;
m_Value = value;
ValueChanged?.Invoke(this);
}
}
public event Action<IVariable> ValueChanged;
public object GetSourceValue(ISelectorInfo _) => Value;
}
カスタムの変数グループ
Custom Variable Group (カスタムの変数グループ) を使用して、カスタム変数を返すことができます。以下の例はその方法を示したものです。
struct ReturnValue : IVariable
{
public object SourceValue { get; set; }
public object GetSourceValue(ISelectorInfo _) => SourceValue;
}
/// <summary>
/// This example shows how a nested group can be used to return custom data without the need for Reflection.
/// </summary>
[DisplayName("Weapon Damage")]
[Serializable]
public class WeaponDamageGroup : IVariableGroup, IVariable
{
public object GetSourceValue(ISelectorInfo _) => this;
public bool TryGetValue(string key, out IVariable value)
{
switch (key)
{
case "sword":
value = new ReturnValue { SourceValue = 6 };
return true;
case "mace":
value = new ReturnValue { SourceValue = 5 };
return true;
case "axe":
value = new ReturnValue { SourceValue = 8 };
return true;
case "dagger":
value = new ReturnValue { SourceValue = 2 };
return true;
}
value = null;
return false;
}
}