mirror of
https://github.com/bdunderscore/modular-avatar.git
synced 2024-12-28 10:15:06 +08:00
feat: Sync Parameter Sequence
This commit is contained in:
parent
2c3e24333a
commit
76eca08c22
101
Editor/Inspector/SyncParameterSequenceEditor.cs
Normal file
101
Editor/Inspector/SyncParameterSequenceEditor.cs
Normal file
@ -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<VRCExpressionParameters>();
|
||||||
|
obj.parameters = Array.Empty<VRCExpressionParameters.Parameter>();
|
||||||
|
obj.isEmpty = true;
|
||||||
|
|
||||||
|
AssetDatabase.CreateAsset(obj, file);
|
||||||
|
Undo.RegisterCreatedObjectUndo(obj, "Create parameter asset");
|
||||||
|
|
||||||
|
_p_parameters.objectReferenceValue = obj;
|
||||||
|
serializedObject.ApplyModifiedProperties();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
Editor/Inspector/SyncParameterSequenceEditor.cs.meta
Normal file
3
Editor/Inspector/SyncParameterSequenceEditor.cs.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: bf6030b7fa704997885767897d1acba0
|
||||||
|
timeCreated: 1733090792
|
@ -153,6 +153,7 @@
|
|||||||
"error.replace_object.null_target:hint": "Replace object needs a target object to replace. Try setting one.",
|
"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.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.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": "[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_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",
|
"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",
|
"ro_sim.effect_group.conditions": "Conditions",
|
||||||
"remove-vertex-color.mode": "Mode",
|
"remove-vertex-color.mode": "Mode",
|
||||||
"remove-vertex-color.mode.Remove": "Remove Vertex Colors",
|
"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"
|
||||||
}
|
}
|
||||||
|
@ -149,6 +149,7 @@
|
|||||||
"error.replace_object.null_target:hint": "Replace Objectは置き換え先のオブジェクトを指定する必要があります。",
|
"error.replace_object.null_target:hint": "Replace Objectは置き換え先のオブジェクトを指定する必要があります。",
|
||||||
"error.replace_object.replacing_replacement": "[MA-0009] 複数のReplace Objectコンポーネントで、同じ置き換え先を指定できません",
|
"error.replace_object.replacing_replacement": "[MA-0009] 複数のReplace Objectコンポーネントで、同じ置き換え先を指定できません",
|
||||||
"error.replace_object.parent_of_target": "[MA-0010] このオブジェクトの親を置き換え先に指定できません",
|
"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": "[MA-1000] このオブジェクトにはSkinned Mesh Rendererがありません。",
|
||||||
"validation.blendshape_sync.no_local_renderer:hint": "Blendshape Syncは同じGameObject上の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がありますが、メッシュがありません。",
|
"validation.blendshape_sync.no_local_mesh": "[MA-1001] このオブジェクトにはSkinned Mesh Rendererがありますが、メッシュがありません。",
|
||||||
@ -279,5 +280,12 @@
|
|||||||
"ro_sim.effect_group.conditions": "条件",
|
"ro_sim.effect_group.conditions": "条件",
|
||||||
"remove-vertex-color.mode": "モード",
|
"remove-vertex-color.mode": "モード",
|
||||||
"remove-vertex-color.mode.Remove": "頂点カラーを削除する",
|
"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": "新しい共用パラメーターアセットを作成します"
|
||||||
}
|
}
|
||||||
|
@ -88,8 +88,9 @@ namespace nadena.dev.modular_avatar.core.editor.plugin
|
|||||||
var maContext = ctx.Extension<ModularAvatarContext>().BuildContext;
|
var maContext = ctx.Extension<ModularAvatarContext>().BuildContext;
|
||||||
FixupExpressionsMenuPass.FixupExpressionsMenu(maContext);
|
FixupExpressionsMenuPass.FixupExpressionsMenu(maContext);
|
||||||
});
|
});
|
||||||
seq.Run(RemoveVertexColorPass.Instance).PreviewingWith(new RemoveVertexColorPreview());
|
seq.Run(SyncParameterSequencePass.Instance);
|
||||||
#endif
|
#endif
|
||||||
|
seq.Run(RemoveVertexColorPass.Instance).PreviewingWith(new RemoveVertexColorPreview());
|
||||||
seq.Run(RebindHumanoidAvatarPass.Instance);
|
seq.Run(RebindHumanoidAvatarPass.Instance);
|
||||||
seq.Run("Purge ModularAvatar components", ctx =>
|
seq.Run("Purge ModularAvatar components", ctx =>
|
||||||
{
|
{
|
||||||
|
118
Editor/SyncParameterSequencePass.cs
Normal file
118
Editor/SyncParameterSequencePass.cs
Normal file
@ -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<SyncParameterSequencePass>
|
||||||
|
{
|
||||||
|
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<ModularAvatarSyncParameterSequence>(true);
|
||||||
|
if (components.Length == 0) return;
|
||||||
|
if (components.Length > 1)
|
||||||
|
{
|
||||||
|
BuildReport.LogFatal("error.singleton", "Sync Parameter Sequence", components.Cast<object>().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<VRCExpressionParameters.Parameter>();
|
||||||
|
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<VRCExpressionParameters.Parameter>().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<VRCExpressionParameters.Parameter>().ToArray();
|
||||||
|
|
||||||
|
EditorUtility.SetDirty(avatarParams);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
Editor/SyncParameterSequencePass.cs.meta
Normal file
3
Editor/SyncParameterSequencePass.cs.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 756425df8aeb4926afceda71bedffa40
|
||||||
|
timeCreated: 1733011801
|
31
Runtime/ModularAvatarSyncParameterSequence.cs
Normal file
31
Runtime/ModularAvatarSyncParameterSequence.cs
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
11
Runtime/ModularAvatarSyncParameterSequence.cs.meta
Normal file
11
Runtime/ModularAvatarSyncParameterSequence.cs.meta
Normal file
@ -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:
|
3
UnitTests~/SyncParameterSequence.meta
Normal file
3
UnitTests~/SyncParameterSequence.meta
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 7b09a147690448ac94d495e90c761c0d
|
||||||
|
timeCreated: 1733093978
|
251
UnitTests~/SyncParameterSequence/SyncParameterSequenceTest.cs
Normal file
251
UnitTests~/SyncParameterSequence/SyncParameterSequenceTest.cs
Normal file
@ -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<VRCAvatarDescriptor>();
|
||||||
|
|
||||||
|
var expParams = ScriptableObject.CreateInstance<VRCExpressionParameters>();
|
||||||
|
|
||||||
|
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<VRCExpressionParameters>();
|
||||||
|
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<ModularAvatarSyncParameterSequence>();
|
||||||
|
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<VRCAvatarDescriptor>();
|
||||||
|
|
||||||
|
var expParams = ScriptableObject.CreateInstance<VRCExpressionParameters>();
|
||||||
|
|
||||||
|
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<VRCExpressionParameters>();
|
||||||
|
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<ModularAvatarSyncParameterSequence>();
|
||||||
|
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<VRCAvatarDescriptor>();
|
||||||
|
|
||||||
|
var expParams = ScriptableObject.CreateInstance<VRCExpressionParameters>();
|
||||||
|
|
||||||
|
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<VRCExpressionParameters>();
|
||||||
|
var paramList = new System.Collections.Generic.List<VRCExpressionParameters.Parameter>();
|
||||||
|
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<ModularAvatarSyncParameterSequence>();
|
||||||
|
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
|
@ -0,0 +1,3 @@
|
|||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 37fcaa6b7094408eac945c2887a1101e
|
||||||
|
timeCreated: 1733093994
|
38
docs~/docs/reference/sync-parameter-sequence.md
Normal file
38
docs~/docs/reference/sync-parameter-sequence.md
Normal file
@ -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.
|
BIN
docs~/docs/reference/sync-parameter-sequence.png
Normal file
BIN
docs~/docs/reference/sync-parameter-sequence.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
@ -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専用のパラメータの
|
||||||
|
両方を多く持っていないことを確認してください。そうしないと、両方の組み合わせを登録して限界を超えることになります。
|
Binary file not shown.
After Width: | Height: | Size: 30 KiB |
Loading…
Reference in New Issue
Block a user