diff --git a/Editor/Inspector/SyncParameterSequenceEditor.cs b/Editor/Inspector/SyncParameterSequenceEditor.cs new file mode 100644 index 00000000..284e5fa1 --- /dev/null +++ b/Editor/Inspector/SyncParameterSequenceEditor.cs @@ -0,0 +1,101 @@ +using System; +using UnityEditor; +using UnityEngine; +using VRC.SDK3.Avatars.ScriptableObjects; +using static nadena.dev.modular_avatar.core.editor.Localization; + +namespace nadena.dev.modular_avatar.core.editor +{ + [CustomEditor(typeof(ModularAvatarSyncParameterSequence))] + [CanEditMultipleObjects] + public class SyncParameterSequenceEditor : MAEditorBase + { + private SerializedProperty _p_platform; + private SerializedProperty _p_parameters; + + private void OnEnable() + { + _p_platform = serializedObject.FindProperty(nameof(ModularAvatarSyncParameterSequence.PrimaryPlatform)); + _p_parameters = serializedObject.FindProperty(nameof(ModularAvatarSyncParameterSequence.Parameters)); + } + + protected override void OnInnerInspectorGUI() + { + serializedObject.Update(); + + EditorGUI.BeginChangeCheck(); + +#if MA_VRCSDK3_AVATARS + var disable = false; +#else + bool disable = true; +#endif + + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + if (disable) + // ReSharper disable HeuristicUnreachableCode + { + EditorGUILayout.HelpBox(S("general.vrcsdk-required"), MessageType.Warning); + } + // ReSharper restore HeuristicUnreachableCode + + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + using (new EditorGUI.DisabledGroupScope(disable)) + { + EditorGUILayout.PropertyField(_p_platform, G("sync-param-sequence.platform")); + GUILayout.BeginHorizontal(); + + var label = G("sync-param-sequence.parameters"); + var sizeCalc = EditorStyles.objectField.CalcSize(label); + EditorGUILayout.PropertyField(_p_parameters, label); + + if (GUILayout.Button(G("sync-param-sequence.create-asset"), + GUILayout.ExpandWidth(false), + GUILayout.Height(sizeCalc.y) + )) + { + CreateParameterAsset(); + } + + GUILayout.EndHorizontal(); + } + + if (EditorGUI.EndChangeCheck()) + { + serializedObject.ApplyModifiedProperties(); + } + + ShowLanguageUI(); + } + + private void CreateParameterAsset() + { +#if MA_VRCSDK3_AVATARS + Transform avatarRoot = null; + if (targets.Length == 1) + { + avatarRoot = + RuntimeUtil.FindAvatarTransformInParents(((ModularAvatarSyncParameterSequence)target).transform); + } + + var assetName = "Avatar"; + if (avatarRoot != null) assetName = avatarRoot.gameObject.name; + + assetName += " SyncedParams"; + + var file = EditorUtility.SaveFilePanelInProject("Create new parameter asset", assetName, "asset", + "Create a new parameter asset"); + + var obj = CreateInstance(); + obj.parameters = Array.Empty(); + obj.isEmpty = true; + + AssetDatabase.CreateAsset(obj, file); + Undo.RegisterCreatedObjectUndo(obj, "Create parameter asset"); + + _p_parameters.objectReferenceValue = obj; + serializedObject.ApplyModifiedProperties(); +#endif + } + } +} \ No newline at end of file diff --git a/Editor/Inspector/SyncParameterSequenceEditor.cs.meta b/Editor/Inspector/SyncParameterSequenceEditor.cs.meta new file mode 100644 index 00000000..e0d762ac --- /dev/null +++ b/Editor/Inspector/SyncParameterSequenceEditor.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: bf6030b7fa704997885767897d1acba0 +timeCreated: 1733090792 \ No newline at end of file diff --git a/Editor/Localization/en-US.json b/Editor/Localization/en-US.json index cc468249..7609d0d0 100644 --- a/Editor/Localization/en-US.json +++ b/Editor/Localization/en-US.json @@ -153,6 +153,7 @@ "error.replace_object.null_target:hint": "Replace object needs a target object to replace. Try setting one.", "error.replace_object.replacing_replacement": "[MA-0009] The same target object cannot be specified in multiple Replace Object components", "error.replace_object.parent_of_target": "[MA-0010] The target object cannot be a parent of this object", + "error.singleton": "[MA-0011] Only one instance of {0} is allowed in an avatar", "validation.blendshape_sync.no_local_renderer": "[MA-1000] No renderer found on this object", "validation.blendshape_sync.no_local_renderer:hint": "Blendshape Sync acts on a Skinned Mesh Renderer on the same GameObject. Did you attach it to the right object?", "validation.blendshape_sync.no_local_mesh": "[MA-1001] No mesh found on the renderer on this object", @@ -287,5 +288,12 @@ "ro_sim.effect_group.conditions": "Conditions", "remove-vertex-color.mode": "Mode", "remove-vertex-color.mode.Remove": "Remove Vertex Colors", - "remove-vertex-color.mode.DontRemove": "Keep Vertex Colors" + "remove-vertex-color.mode.DontRemove": "Keep Vertex Colors", + "general.vrcsdk-required": "This component requires the VRCSDK to function.", + "sync-param-sequence.platform": "Primary Platform", + "sync-param-sequence.platform.tooltip": "When building for this platform, Modular Avatar will record all expression parameters for use on other platform builds", + "sync-param-sequence.parameters": "Common parameters asset", + "sync-param-sequence.parameters.tooltip": "The asset to store common parameters in. Do not use the same Expression Parameters that you have set in your avatar descriptor.", + "sync-param-sequence.create-asset": "New", + "sync-param-sequence.create-asset.tooltip": "Creates a new expression parameters asset" } diff --git a/Editor/Localization/ja-JP.json b/Editor/Localization/ja-JP.json index 14640e70..269545ff 100644 --- a/Editor/Localization/ja-JP.json +++ b/Editor/Localization/ja-JP.json @@ -149,6 +149,7 @@ "error.replace_object.null_target:hint": "Replace Objectは置き換え先のオブジェクトを指定する必要があります。", "error.replace_object.replacing_replacement": "[MA-0009] 複数のReplace Objectコンポーネントで、同じ置き換え先を指定できません", "error.replace_object.parent_of_target": "[MA-0010] このオブジェクトの親を置き換え先に指定できません", + "error.singleton": "[MA-0011] {0} はアバターに一個しか存在できません", "validation.blendshape_sync.no_local_renderer": "[MA-1000] このオブジェクトにはSkinned Mesh Rendererがありません。", "validation.blendshape_sync.no_local_renderer:hint": "Blendshape Syncは同じGameObject上のSkinned Mesh Rendererに作用します。コンポーネントが正しいオブジェクトに追加されているか確認してください。", "validation.blendshape_sync.no_local_mesh": "[MA-1001] このオブジェクトにはSkinned Mesh Rendererがありますが、メッシュがありません。", @@ -279,5 +280,12 @@ "ro_sim.effect_group.conditions": "条件", "remove-vertex-color.mode": "モード", "remove-vertex-color.mode.Remove": "頂点カラーを削除する", - "remove-vertex-color.mode.DontRemove": "頂点カラーを削除しない" + "remove-vertex-color.mode.DontRemove": "頂点カラーを削除しない", + "general.vrcsdk-required": "このコンポーネントにはVRCSDKが必要です。", + "sync-param-sequence.platform": "主要プラットホーム", + "sync-param-sequence.platform.tooltip": "このプラットホームでビルドすると、他のプラットホームを合わせるためにパラメーターを記録します。", + "sync-param-sequence.parameters": "共用パラメーターアセット", + "sync-param-sequence.parameters.tooltip": "共用パラメーターがこのアセットに保持されます。アバターデスクリプターに使われるアセットを流用しないでください。", + "sync-param-sequence.create-asset": "新規作成", + "sync-param-sequence.create-asset.tooltip": "新しい共用パラメーターアセットを作成します" } diff --git a/Editor/PluginDefinition/PluginDefinition.cs b/Editor/PluginDefinition/PluginDefinition.cs index 9be466c9..ffc77033 100644 --- a/Editor/PluginDefinition/PluginDefinition.cs +++ b/Editor/PluginDefinition/PluginDefinition.cs @@ -88,8 +88,9 @@ namespace nadena.dev.modular_avatar.core.editor.plugin var maContext = ctx.Extension().BuildContext; FixupExpressionsMenuPass.FixupExpressionsMenu(maContext); }); - seq.Run(RemoveVertexColorPass.Instance).PreviewingWith(new RemoveVertexColorPreview()); + seq.Run(SyncParameterSequencePass.Instance); #endif + seq.Run(RemoveVertexColorPass.Instance).PreviewingWith(new RemoveVertexColorPreview()); seq.Run(RebindHumanoidAvatarPass.Instance); seq.Run("Purge ModularAvatar components", ctx => { diff --git a/Editor/SyncParameterSequencePass.cs b/Editor/SyncParameterSequencePass.cs new file mode 100644 index 00000000..e6e83d37 --- /dev/null +++ b/Editor/SyncParameterSequencePass.cs @@ -0,0 +1,118 @@ +#nullable enable + +using System; +using System.Collections.Specialized; +using System.Linq; +using nadena.dev.modular_avatar.editor.ErrorReporting; +using nadena.dev.ndmf; +using UnityEditor; +using VRC.SDK3.Avatars.ScriptableObjects; +using static nadena.dev.modular_avatar.core.ModularAvatarSyncParameterSequence; +using Object = UnityEngine.Object; + +namespace nadena.dev.modular_avatar.core.editor +{ + public class SyncParameterSequencePass : Pass + { + private static Platform? CurrentPlatform + { + get + { + switch (EditorUserBuildSettings.activeBuildTarget) + { + case BuildTarget.Android: return Platform.Android; + case BuildTarget.iOS: return Platform.iOS; + case BuildTarget.StandaloneWindows64: return Platform.PC; + case BuildTarget.StandaloneLinux64: return Platform.PC; // for CI + default: return null; + } + } + } + + protected override void Execute(ndmf.BuildContext context) + { + ExecuteStatic(context); + } + + internal static void ExecuteStatic(ndmf.BuildContext context) + { + var avDesc = context.AvatarDescriptor; + + var components = context.AvatarRootObject.GetComponentsInChildren(true); + if (components.Length == 0) return; + if (components.Length > 1) + { + BuildReport.LogFatal("error.singleton", "Sync Parameter Sequence", components.Cast().ToArray()); + return; + } + + var syncComponent = components[0]; + if (syncComponent.Parameters == null) return; + + if (avDesc.expressionParameters == null) return; + var avatarParams = avDesc.expressionParameters; + + if (!context.IsTemporaryAsset(avatarParams)) + { + avatarParams = Object.Instantiate(avatarParams); + avDesc.expressionParameters = avatarParams; + } + + if (syncComponent.Parameters.parameters == null) + { + syncComponent.Parameters.parameters = Array.Empty(); + EditorUtility.SetDirty(syncComponent.Parameters); + } + + // If we're on the primary platform, add in any unknown parameters, and prune if we exceed the limit. + if (CurrentPlatform != null && CurrentPlatform == syncComponent.PrimaryPlatform) + { + var registered = new OrderedDictionary(); + + foreach (var param in syncComponent.Parameters.parameters) + { + if (param == null) continue; + if (!param.networkSynced) continue; + registered[param.name] = param; + } + + foreach (var param in avatarParams.parameters) + { + if (param == null) continue; + if (!param.networkSynced) continue; + registered[param.name] = param; + } + + syncComponent.Parameters.parameters = registered.Values.Cast().ToArray(); + if (!syncComponent.Parameters.IsWithinBudget()) + { + var knownParams = avatarParams.parameters.Where(p => p != null).Select(p => p.name).ToHashSet(); + syncComponent.Parameters.parameters = syncComponent.Parameters.parameters.Where( + p => p != null && knownParams.Contains(p.name) + ).ToArray(); + } + + EditorUtility.SetDirty(syncComponent.Parameters); + } + + // Now copy back... + OrderedDictionary finalParams = new(); + foreach (var param in syncComponent.Parameters.parameters) + { + if (param == null) continue; + if (!param.networkSynced) continue; + finalParams[param.name] = param; + } + + foreach (var param in avatarParams.parameters) + { + if (param == null) continue; + finalParams[param.name] = param; + } + + avatarParams.parameters = finalParams.Values.Cast().ToArray(); + + EditorUtility.SetDirty(avatarParams); + } + } +} \ No newline at end of file diff --git a/Editor/SyncParameterSequencePass.cs.meta b/Editor/SyncParameterSequencePass.cs.meta new file mode 100644 index 00000000..cd61ea01 --- /dev/null +++ b/Editor/SyncParameterSequencePass.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 756425df8aeb4926afceda71bedffa40 +timeCreated: 1733011801 \ No newline at end of file diff --git a/Runtime/ModularAvatarSyncParameterSequence.cs b/Runtime/ModularAvatarSyncParameterSequence.cs new file mode 100644 index 00000000..e721a9ef --- /dev/null +++ b/Runtime/ModularAvatarSyncParameterSequence.cs @@ -0,0 +1,31 @@ +using System; +using JetBrains.Annotations; +using UnityEngine; +using VRC.SDK3.Avatars.ScriptableObjects; + +namespace nadena.dev.modular_avatar.core +{ + [AddComponentMenu("Modular Avatar/MA Sync Parameter Sequence")] + [DisallowMultipleComponent] + [HelpURL("https://modular-avatar.nadena.dev/docs/reference/sync-parameter-sequence?lang=auto")] + [PublicAPI] + public class ModularAvatarSyncParameterSequence : AvatarTagComponent + { + [Serializable] + [PublicAPI] + public enum Platform + { + PC, + Android, + iOS + } + + public Platform PrimaryPlatform = Platform.Android; + #if MA_VRCSDK3_AVATARS + public VRCExpressionParameters Parameters; + #else + // preserve settings on non-VRC platforms at least + public UnityEngine.Object Parameters; + #endif + } +} \ No newline at end of file diff --git a/Runtime/ModularAvatarSyncParameterSequence.cs.meta b/Runtime/ModularAvatarSyncParameterSequence.cs.meta new file mode 100644 index 00000000..051758ba --- /dev/null +++ b/Runtime/ModularAvatarSyncParameterSequence.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 934543afe4744213b5621aa13a67e3b4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: a8edd5bd1a0a64a40aa99cc09fb5f198, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/UnitTests~/SyncParameterSequence.meta b/UnitTests~/SyncParameterSequence.meta new file mode 100644 index 00000000..ef65c7de --- /dev/null +++ b/UnitTests~/SyncParameterSequence.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 7b09a147690448ac94d495e90c761c0d +timeCreated: 1733093978 \ No newline at end of file diff --git a/UnitTests~/SyncParameterSequence/SyncParameterSequenceTest.cs b/UnitTests~/SyncParameterSequence/SyncParameterSequenceTest.cs new file mode 100644 index 00000000..73c5eb33 --- /dev/null +++ b/UnitTests~/SyncParameterSequence/SyncParameterSequenceTest.cs @@ -0,0 +1,251 @@ +#if MA_VRCSDK3_AVATARS + +using modular_avatar_tests; +using nadena.dev.modular_avatar.core; +using nadena.dev.modular_avatar.core.editor; +using NUnit.Framework; +using UnityEditor; +using UnityEngine; +using VRC.SDK3.Avatars.Components; +using VRC.SDK3.Avatars.ScriptableObjects; + +namespace UnitTests.SyncParameterSequence +{ + public class SyncParameterSequenceTest : TestBase + { + [Test] + public void NonPrimaryPlatform() + { + ModularAvatarSyncParameterSequence.Platform platform; + switch (EditorUserBuildSettings.activeBuildTarget) + { + case BuildTarget.Android: + platform = ModularAvatarSyncParameterSequence.Platform.PC; + break; + default: + platform = ModularAvatarSyncParameterSequence.Platform.Android; + break; + } + + var root = CreateRoot("root"); + var avdesc = root.GetComponent(); + + var expParams = ScriptableObject.CreateInstance(); + + expParams.parameters = new[] + { + new VRCExpressionParameters.Parameter() + { + name = "p1", + valueType = VRCExpressionParameters.ValueType.Bool, + networkSynced = true, + defaultValue = 0.5f, + }, + new VRCExpressionParameters.Parameter() + { + name = "p2", + valueType = VRCExpressionParameters.ValueType.Int, + networkSynced = true, + defaultValue = 0.5f, + } + }; + + var refParams = ScriptableObject.CreateInstance(); + refParams.parameters = new[] + { + new VRCExpressionParameters.Parameter() + { + name = "p0", + valueType = VRCExpressionParameters.ValueType.Bool, + networkSynced = true + }, + new VRCExpressionParameters.Parameter() + { + name = "p2", + valueType = VRCExpressionParameters.ValueType.Int, + networkSynced = true + } + }; + + var c = avdesc.gameObject.AddComponent(); + c.PrimaryPlatform = platform; + c.Parameters = refParams; + + avdesc.expressionParameters = expParams; + + var context = CreateContext(root); + SyncParameterSequencePass.ExecuteStatic(context); + + expParams = avdesc.expressionParameters; + + Assert.AreEqual("p0", expParams.parameters[0].name); + Assert.AreEqual("p2", expParams.parameters[1].name); + Assert.AreEqual("p1", expParams.parameters[2].name); + + Assert.IsTrue(Mathf.Approximately(0f, expParams.parameters[0].defaultValue)); + Assert.IsTrue(Mathf.Approximately(0.5f, expParams.parameters[1].defaultValue)); + Assert.IsTrue(Mathf.Approximately(0.5f, expParams.parameters[2].defaultValue)); + + Assert.AreEqual(2, refParams.parameters.Length); + } + + [Test] + public void PrimaryPlatform() + { + ModularAvatarSyncParameterSequence.Platform platform; + switch (EditorUserBuildSettings.activeBuildTarget) + { + case BuildTarget.Android: + platform = ModularAvatarSyncParameterSequence.Platform.Android; + break; + default: + platform = ModularAvatarSyncParameterSequence.Platform.PC; + break; + } + + var root = CreateRoot("root"); + var avdesc = root.GetComponent(); + + var expParams = ScriptableObject.CreateInstance(); + + expParams.parameters = new[] + { + new VRCExpressionParameters.Parameter() + { + name = "p1", + valueType = VRCExpressionParameters.ValueType.Bool, + networkSynced = true, + defaultValue = 0.5f, + }, + new VRCExpressionParameters.Parameter() + { + name = "p2", + valueType = VRCExpressionParameters.ValueType.Int, + networkSynced = true, + defaultValue = 0.5f, + }, + new VRCExpressionParameters.Parameter() { + name = "notsynced", + valueType = VRCExpressionParameters.ValueType.Int, + networkSynced = false, + } + }; + + var refParams = ScriptableObject.CreateInstance(); + refParams.parameters = new[] + { + new VRCExpressionParameters.Parameter() + { + name = "p0", + valueType = VRCExpressionParameters.ValueType.Bool, + networkSynced = true + }, + new VRCExpressionParameters.Parameter() + { + name = "p2", + valueType = VRCExpressionParameters.ValueType.Int, + networkSynced = true + } + }; + + var c = avdesc.gameObject.AddComponent(); + c.PrimaryPlatform = platform; + c.Parameters = refParams; + + avdesc.expressionParameters = expParams; + + var context = CreateContext(root); + SyncParameterSequencePass.ExecuteStatic(context); + + expParams = avdesc.expressionParameters; + + Assert.AreEqual("p0", expParams.parameters[0].name); + Assert.AreEqual("p2", expParams.parameters[1].name); + Assert.AreEqual("p1", expParams.parameters[2].name); + Assert.AreEqual("notsynced", expParams.parameters[3].name); + + Assert.IsTrue(Mathf.Approximately(0f, expParams.parameters[0].defaultValue)); + Assert.IsTrue(Mathf.Approximately(0.5f, expParams.parameters[1].defaultValue)); + Assert.IsTrue(Mathf.Approximately(0.5f, expParams.parameters[2].defaultValue)); + + Assert.AreEqual(3, refParams.parameters.Length); + Assert.AreEqual("p0", refParams.parameters[0].name); + Assert.AreEqual("p2", refParams.parameters[1].name); + Assert.AreEqual("p1", refParams.parameters[2].name); + } + + + [Test] + public void PrimaryPlatformOverflow() + { + ModularAvatarSyncParameterSequence.Platform platform; + switch (EditorUserBuildSettings.activeBuildTarget) + { + case BuildTarget.Android: + platform = ModularAvatarSyncParameterSequence.Platform.Android; + break; + default: + platform = ModularAvatarSyncParameterSequence.Platform.PC; + break; + } + + var root = CreateRoot("root"); + var avdesc = root.GetComponent(); + + var expParams = ScriptableObject.CreateInstance(); + + expParams.parameters = new[] + { + new VRCExpressionParameters.Parameter() + { + name = "p1", + valueType = VRCExpressionParameters.ValueType.Bool, + networkSynced = true, + defaultValue = 0.5f, + }, + new VRCExpressionParameters.Parameter() + { + name = "p2", + valueType = VRCExpressionParameters.ValueType.Int, + networkSynced = true, + defaultValue = 0.5f, + } + }; + + var refParams = ScriptableObject.CreateInstance(); + var paramList = new System.Collections.Generic.List(); + for (int i = 0; i < VRCExpressionParameters.MAX_PARAMETER_COST; i++) + { + paramList.Add(new() + { + name = "b" + i, + valueType = VRCExpressionParameters.ValueType.Bool, + networkSynced = true + }); + } + + refParams.parameters = paramList.ToArray(); + + var c = avdesc.gameObject.AddComponent(); + c.PrimaryPlatform = platform; + c.Parameters = refParams; + + avdesc.expressionParameters = expParams; + + var context = CreateContext(root); + SyncParameterSequencePass.ExecuteStatic(context); + + expParams = avdesc.expressionParameters; + + Assert.AreEqual(2, expParams.parameters.Length); + Assert.AreEqual("p1", expParams.parameters[0].name); + Assert.AreEqual("p2", expParams.parameters[1].name); + + Assert.AreEqual(2, refParams.parameters.Length); + Assert.AreEqual("p1", refParams.parameters[0].name); + Assert.AreEqual("p2", refParams.parameters[1].name); + } + } +} + +#endif \ No newline at end of file diff --git a/UnitTests~/SyncParameterSequence/SyncParameterSequenceTest.cs.meta b/UnitTests~/SyncParameterSequence/SyncParameterSequenceTest.cs.meta new file mode 100644 index 00000000..9611d253 --- /dev/null +++ b/UnitTests~/SyncParameterSequence/SyncParameterSequenceTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 37fcaa6b7094408eac945c2887a1101e +timeCreated: 1733093994 \ No newline at end of file diff --git a/docs~/docs/reference/sync-parameter-sequence.md b/docs~/docs/reference/sync-parameter-sequence.md new file mode 100644 index 00000000..1505195f --- /dev/null +++ b/docs~/docs/reference/sync-parameter-sequence.md @@ -0,0 +1,38 @@ +# Sync Parameter Sequence + +![Sync Parameter Sequence](sync-parameter-sequence.png) + +On VRChat, it's necessary for parameters that are shared between different-platforms of an avatar (e.g. PC and Android) +to appear at the start of the expressions parameters list, and in the same order. This component adjusts the order of +your expressions parameters, and adds additional parameters where necessary, to ensure that your avatar syncs properly +between PC and Android. + +## When should I use it? + +You should use this component if you are uploading different versions of the same avatar to PC and Android, and both +versions make use of synced expressions parameters. + +## When shouldn't I use it? + +This component may have compatibility issues with certain VRCFury components, such as Parameter Compressor. + +## How should I use it? + +First, attach the Sync Parameter Sequence component to any object on your avatar. Then, click the New button to create +an asset to save the parameter sequence. On other platform variants of your avatar, attach the component, and select the +asset you just created. Upload on Android (or whichever platform you want to be the primary platform), then upload for +other platforms as well. + +Whenever you upload your avatar on the platform listed as "Primary Platform", Modular Avatar will record its expression +parameters in this asset. Then, later, when you upload on some other platform, Modular Avatar will adjust the order of +the parameters to match the primary platform. + +## Parameter limits + +The Sync Parameter Sequence component will add additional parameters to your avatar if necessary to ensure that the +order of parameters matches between platforms. This may cause your avatar to exceed the maximum number of parameters, +in which case the build will fail. + +To address this, you can clear the contents of the parameters asset to clear out obsolete parameters; otherwise, make +sure you don't have a lot of both android-only and PC-only parameters, because you'll end up using the combination of +both. \ No newline at end of file diff --git a/docs~/docs/reference/sync-parameter-sequence.png b/docs~/docs/reference/sync-parameter-sequence.png new file mode 100644 index 00000000..0bc7c83f Binary files /dev/null and b/docs~/docs/reference/sync-parameter-sequence.png differ diff --git a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/sync-parameter-sequence.md b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/sync-parameter-sequence.md new file mode 100644 index 00000000..11e7ffee --- /dev/null +++ b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/sync-parameter-sequence.md @@ -0,0 +1,32 @@ +# Sync Parameter Sequence + +![Sync Parameter Sequence](sync-parameter-sequence.png) + +VRChatでは、異なるプラットフォーム間で共有されるパラメータ(例:PCとAndroid)が、パラメータリストの先頭に、同じ順序で登録してある必要が +あります。このコンポーネントは、アバターがPCとAndroid間で正しく同期するために、表情パラメータの順序を調整し、必要に応じてパラメータを追加します。 + +## いつ使うべきか? + +同じアバターの異なるバージョンをPCとAndroidにアップロードし、両方のバージョンが同期パラメータを使用する場合、このコンポーネントを使用すると +便利です。 + +## 使わない方がいい場合 + +このコンポーネントは、Parameter Compressorなど一部のVRCFuryコンポーネントと互換性問題がある可能性があります。 + +## 使い方 + +まず、Sync Parameter Sequenceコンポーネントをアバターの任意のオブジェクトに追加します。次に、「新規作成」ボタンをクリックして、パラメータ +順序を保存するアセットを作成します。アバターの他のプラットフォーム用のバージョンに同じくコンポーネントを追加し、作成したアセットをセットします。 +Android(あるいは主要プラットホームで選択したプラットフォーム)でアップロードし、そのあとに他のプラットフォームにもアップロードします。 + +「主要プラットフォーム」として設定されたプラットフォームでアバターをアップロードするたびに、Modular Avatarはパラメータリストをアセットに +記録します。その後、他のプラットフォームにアップロードする際に、Modular Avatarはパラメータの順序を主要プラットフォームに合わせて調整します。 + +## パラメータ制限について + +Sync Parameter Sequenceコンポーネントは、パラメータの順序が一致するように必要に応じてアバターにパラメータを追加します。これにより、 +アバターがパラメータの最大数を超える可能性があり、ビルドが失敗することがあります。 + +解決するために、パラメータアセットの内容をクリアして、不要なパラメータを削除するか、Android専用のパラメータとPC専用のパラメータの +両方を多く持っていないことを確認してください。そうしないと、両方の組み合わせを登録して限界を超えることになります。 \ No newline at end of file diff --git a/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/sync-parameter-sequence.png b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/sync-parameter-sequence.png new file mode 100644 index 00000000..5b770059 Binary files /dev/null and b/docs~/i18n/ja/docusaurus-plugin-content-docs/current/reference/sync-parameter-sequence.png differ