Files

1049 lines
45 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
using VRC.SDK3.Editor;
#if VRC_SDK_VRCSDK3
using VRC.SDK3.Components;
#endif
using VRC.SDKBase.Editor;
using VRC.SDKBase.Editor.BuildPipeline;
#if UDON
using VRC.Udon;
using VRC.Udon.Common.Interfaces;
#endif
using Object = UnityEngine.Object;
[assembly: VRCSdkControlPanelBuilder(typeof(VRCSdkControlPanelWorldBuilder))]
namespace VRC.SDKBase.Editor
{
public class VRCSdkControlPanelWorldBuilder : IVRCSdkControlPanelBuilder
{
private static Type _postProcessVolumeType;
private VRCSdkControlPanel _builder;
private VRC_SceneDescriptor[] _scenes;
private Vector2 _scrollPos;
private Vector2 _builderScrollPos;
private int _numClients = 1;
private bool _forceNoVr;
public void SelectAllComponents()
{
List<Object> show = new List<Object>(Selection.objects);
foreach (VRC_SceneDescriptor s in _scenes)
show.Add(s.gameObject);
Selection.objects = show.ToArray();
}
public void ShowSettingsOptions()
{
EditorGUILayout.BeginVertical(VRCSdkControlPanel.boxGuiStyle);
GUILayout.Label("World Options", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
int prevLineMode = _builder.triggerLineMode;
int lineMode = Convert.ToInt32(EditorGUILayout.EnumPopup("Trigger Lines", (VRC_Trigger.EditorTriggerLineMode)_builder.triggerLineMode, GUILayout.Width(250)));
if (lineMode != prevLineMode)
{
_builder.triggerLineMode = lineMode;
foreach (GameObject t in Selection.gameObjects)
{
EditorUtility.SetDirty(t);
}
}
GUILayout.Space(10);
switch ((VRC_Trigger.EditorTriggerLineMode)_builder.triggerLineMode)
{
case VRC_Trigger.EditorTriggerLineMode.Enabled:
EditorGUILayout.LabelField("Lines shown for all selected triggers", EditorStyles.miniLabel);
break;
case VRC_Trigger.EditorTriggerLineMode.Disabled:
EditorGUILayout.LabelField("No trigger lines are drawn", EditorStyles.miniLabel);
break;
case VRC_Trigger.EditorTriggerLineMode.PerTrigger:
EditorGUILayout.LabelField("Toggle lines directly on each trigger component", EditorStyles.miniLabel);
break;
}
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
}
public bool IsValidBuilder(out string message)
{
FindScenes();
message = null;
if (_scenes != null && _scenes.Length > 0) return true;
#if VRC_SDK_VRCSDK2
message = "A VRC_SceneDescriptor or VRC_AvatarDescriptor\nis required to build VRChat SDK Content";
#elif VRC_SDK_VRCSDK3
message = "A VRCSceneDescriptor or VRCAvatarDescriptor\nis required to build VRChat SDK Content";
#endif
return false;
}
public void ShowBuilder()
{
if (_postProcessVolumeType != null)
{
if (Camera.main != null && Camera.main.GetComponentInChildren(_postProcessVolumeType))
{
_builder.OnGUIWarning(null,
"Scene has a PostProcessVolume on the Reference Camera (Main Camera). This Camera is disabled at runtime. Please move the PostProcessVolume to another GameObject.",
() => { Selection.activeGameObject = Camera.main.gameObject; },
TryMovePostProcessVolumeAwayFromMainCamera
);
}
}
#if UDON
{
List<UdonBehaviour> failedBehaviours = ShouldShowPrimitivesWarning();
if (failedBehaviours.Count > 0)
{
_builder.OnGUIWarning(null,
"Udon Objects reference builtin Unity mesh assets, this won't work. Consider making a copy of the mesh to use instead.",
() =>
{
Selection.objects = failedBehaviours.Select(s => s.gameObject).Cast<Object>().ToArray();
}, FixPrimitivesWarning);
}
}
#endif
if (_scenes.Length > 1)
{
Object[] gos = new Object[_scenes.Length];
for (int i = 0; i < _scenes.Length; ++i)
gos[i] = _scenes[i].gameObject;
_builder.OnGUIError(null,
"A Unity scene containing a VRChat Scene Descriptor should only contain one Scene Descriptor.",
delegate { Selection.objects = gos; }, null);
EditorGUILayout.Separator();
GUILayout.BeginVertical(GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth));
_builder.OnGUIShowIssues();
GUILayout.EndVertical();
}
else if (_scenes.Length == 1)
{
bool inScrollView = true;
_scrollPos = GUILayout.BeginScrollView(_scrollPos, false, false, GUIStyle.none,
GUI.skin.verticalScrollbar, GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth));
try
{
bool setupRequired = OnGUISceneSetup();
if (!setupRequired)
{
if (!_builder.CheckedForIssues)
{
_builder.ResetIssues();
OnGUISceneCheck(_scenes[0]);
_builder.CheckedForIssues = true;
}
OnGUISceneSettings(_scenes[0]);
_builder.OnGUIShowIssues();
_builder.OnGUIShowIssues(_scenes[0]);
GUILayout.FlexibleSpace();
GUILayout.EndScrollView();
inScrollView = false;
OnGUIScene();
}
else
{
_builder.OnGuiFixIssuesToBuildOrTest();
GUILayout.EndScrollView();
}
}
catch (Exception)
{
if (inScrollView)
GUILayout.EndScrollView();
}
}
else
{
EditorGUILayout.Space();
if (UnityEditor.BuildPipeline.isBuildingPlayer)
{
GUILayout.Space(20);
EditorGUILayout.LabelField("Building – Please Wait ...", VRCSdkControlPanel.titleGuiStyle,
GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth));
}
}
}
public void RegisterBuilder(VRCSdkControlPanel baseBuilder)
{
_builder = baseBuilder;
}
private void TryMovePostProcessVolumeAwayFromMainCamera()
{
if (Camera.main == null)
return;
if (_postProcessVolumeType == null)
return;
Component oldVolume = Camera.main.GetComponentInChildren(_postProcessVolumeType);
if (!oldVolume)
return;
GameObject oldObject = oldVolume.gameObject;
GameObject newObject = Object.Instantiate(oldObject);
newObject.name = "Post Processing Volume";
newObject.tag = "Untagged";
foreach (Transform child in newObject.transform)
{
Object.DestroyImmediate(child.gameObject);
}
var newVolume = newObject.GetComponentInChildren(_postProcessVolumeType);
foreach (Component c in newObject.GetComponents<Component>())
{
if ((c == newObject.transform) || (c == newVolume))
continue;
Object.DestroyImmediate(c);
}
Object.DestroyImmediate(oldVolume);
_builder.Repaint();
Selection.activeGameObject = newObject;
}
[UnityEditor.Callbacks.DidReloadScripts(int.MaxValue)]
static void DidReloadScripts()
{
DetectPostProcessingPackage();
}
static void DetectPostProcessingPackage()
{
_postProcessVolumeType = null;
try
{
System.Reflection.Assembly
postProcAss = System.Reflection.Assembly.Load("Unity.PostProcessing.Runtime");
_postProcessVolumeType = postProcAss.GetType("UnityEngine.Rendering.PostProcessing.PostProcessVolume");
}
catch
{
// -> post processing not installed
}
}
#if UDON
private static Mesh[] _primitiveMeshes;
private static List<UdonBehaviour> ShouldShowPrimitivesWarning()
{
if (_primitiveMeshes == null)
{
PrimitiveType[] primitiveTypes = (PrimitiveType[]) Enum.GetValues(typeof(PrimitiveType));
_primitiveMeshes = new Mesh[primitiveTypes.Length];
for (int i = 0; i < primitiveTypes.Length; i++)
{
PrimitiveType primitiveType = primitiveTypes[i];
GameObject go = GameObject.CreatePrimitive(primitiveType);
_primitiveMeshes[i] = go.GetComponent<MeshFilter>().sharedMesh;
Object.DestroyImmediate(go);
}
}
UdonBehaviour[] allBehaviours = Object.FindObjectsOfType<UdonBehaviour>();
List<UdonBehaviour> failedBehaviours = new List<UdonBehaviour>(allBehaviours.Length);
foreach (UdonBehaviour behaviour in allBehaviours)
{
IUdonVariableTable publicVariables = behaviour.publicVariables;
foreach (string symbol in publicVariables.VariableSymbols)
{
if (!publicVariables.TryGetVariableValue(symbol, out Mesh mesh))
{
continue;
}
if (mesh == null)
{
continue;
}
bool all = true;
foreach (Mesh primitiveMesh in _primitiveMeshes)
{
if (mesh != primitiveMesh)
{
continue;
}
all = false;
break;
}
if (all)
{
continue;
}
failedBehaviours.Add(behaviour);
}
}
return failedBehaviours;
}
private void FixPrimitivesWarning()
{
UdonBehaviour[] allObjects = Object.FindObjectsOfType<UdonBehaviour>();
foreach (UdonBehaviour behaviour in allObjects)
{
IUdonVariableTable publicVariables = behaviour.publicVariables;
foreach (string symbol in publicVariables.VariableSymbols)
{
if (!publicVariables.TryGetVariableValue(symbol, out Mesh mesh))
{
continue;
}
if (mesh == null)
{
continue;
}
bool all = true;
foreach (Mesh primitiveMesh in _primitiveMeshes)
{
if (mesh != primitiveMesh)
{
continue;
}
all = false;
break;
}
if (all)
{
continue;
}
Mesh clone = Object.Instantiate(mesh);
Scene scene = behaviour.gameObject.scene;
string scenePath = Path.GetDirectoryName(scene.path) ?? "Assets";
string folderName = $"{scene.name}_MeshClones";
string folderPath = Path.Combine(scenePath, folderName);
if (!AssetDatabase.IsValidFolder(folderPath))
{
AssetDatabase.CreateFolder(scenePath, folderName);
}
string assetPath = Path.Combine(folderPath, $"{clone.name}.asset");
Mesh existingClone = AssetDatabase.LoadAssetAtPath<Mesh>(assetPath);
if (existingClone == null)
{
AssetDatabase.CreateAsset(clone, assetPath);
AssetDatabase.Refresh();
}
else
{
clone = existingClone;
}
publicVariables.TrySetVariableValue(symbol, clone);
EditorSceneManager.MarkSceneDirty(behaviour.gameObject.scene);
}
}
}
#endif
private void FindScenes()
{
VRC_SceneDescriptor[] newScenes = Tools.FindSceneObjectsOfTypeAll<VRC_SceneDescriptor>();
if (_scenes != null)
{
foreach (VRC_SceneDescriptor s in newScenes)
if (_scenes.Contains(s) == false)
_builder.CheckedForIssues = false;
}
_scenes = newScenes;
}
private static bool OnGUISceneSetup()
{
bool mandatoryExpand = !UpdateLayers.AreLayersSetup() || !UpdateLayers.IsCollisionLayerMatrixSetup();
if (mandatoryExpand)
EditorGUILayout.LabelField("VRChat Scene Setup", VRCSdkControlPanel.titleGuiStyle,
GUILayout.Height(50));
if (!UpdateLayers.AreLayersSetup())
{
GUILayout.BeginVertical(VRCSdkControlPanel.boxGuiStyle, GUILayout.Height(100),
GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth));
GUILayout.BeginHorizontal();
GUILayout.BeginVertical(GUILayout.Width(300));
EditorGUILayout.Space();
GUILayout.Label("Layers", VRCSdkControlPanel.infoGuiStyle);
GUILayout.Label(
"VRChat scenes must have the same Unity layer configuration as VRChat so we can all predict things like physics and collisions. Pressing this button will configure your project's layers to match VRChat.",
VRCSdkControlPanel.infoGuiStyle, GUILayout.Width(300));
GUILayout.EndVertical();
GUILayout.BeginVertical(GUILayout.Width(150));
GUILayout.Label("", GUILayout.Height(15));
if (UpdateLayers.AreLayersSetup())
{
GUILayout.Label("Step Complete!", VRCSdkControlPanel.infoGuiStyle);
}
else if (GUILayout.Button("Setup Layers for VRChat", GUILayout.Width(172)))
{
bool doIt = EditorUtility.DisplayDialog("Setup Layers for VRChat",
"This adds all VRChat layers to your project and pushes any custom layers down the layer list. If you have custom layers assigned to gameObjects, you'll need to reassign them. Are you sure you want to continue?",
"Do it!", "Don't do it");
if (doIt)
UpdateLayers.SetupEditorLayers();
}
GUILayout.EndVertical();
GUILayout.EndHorizontal();
GUILayout.EndVertical();
GUILayout.Space(10);
}
if (!UpdateLayers.IsCollisionLayerMatrixSetup())
{
GUILayout.BeginVertical(VRCSdkControlPanel.boxGuiStyle, GUILayout.Height(100),
GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth));
GUILayout.BeginHorizontal();
GUILayout.BeginVertical(GUILayout.Width(300));
EditorGUILayout.Space();
GUILayout.Label("Collision Matrix", VRCSdkControlPanel.infoGuiStyle);
GUILayout.Label(
"VRChat uses specific layers for collision. In order for testing and development to run smoothly it is necessary to configure your project's collision matrix to match that of VRChat.",
VRCSdkControlPanel.infoGuiStyle, GUILayout.Width(300));
GUILayout.EndVertical();
GUILayout.BeginVertical(GUILayout.Width(150));
GUILayout.Label("", GUILayout.Height(15));
if (UpdateLayers.AreLayersSetup() == false)
{
GUILayout.Label("You must first configure your layers for VRChat to proceed. Please see above.",
VRCSdkControlPanel.infoGuiStyle);
}
else if (UpdateLayers.IsCollisionLayerMatrixSetup())
{
GUILayout.Label("Step Complete!", VRCSdkControlPanel.infoGuiStyle);
}
else
{
if (GUILayout.Button("Set Collision Matrix", GUILayout.Width(172)))
{
bool doIt = EditorUtility.DisplayDialog("Setup Collision Layer Matrix for VRChat",
"This will setup the correct physics collisions in the PhysicsManager for VRChat layers. Are you sure you want to continue?",
"Do it!", "Don't do it");
if (doIt)
{
UpdateLayers.SetupCollisionLayerMatrix();
}
}
}
GUILayout.EndVertical();
GUILayout.EndHorizontal();
GUILayout.EndVertical();
GUILayout.Space(10);
}
return mandatoryExpand;
}
private void OnGUISceneCheck(VRC_SceneDescriptor scene)
{
CheckUploadChanges(scene);
#if !VRC_SDK_VRCSDK3
bool isSdk3Scene = false;
#else
bool isSdk3Scene = scene as VRCSceneDescriptor != null;
#endif
List<VRC_EventHandler> sdkBaseEventHandlers =
new List<VRC_EventHandler>(Object.FindObjectsOfType<VRC_EventHandler>());
#if VRC_SDK_VRCSDK2
if (isSdk3Scene == false)
{
for (int i = sdkBaseEventHandlers.Count - 1; i >= 0; --i)
if (sdkBaseEventHandlers[i] as VRCSDK2.VRC_EventHandler)
sdkBaseEventHandlers.RemoveAt(i);
}
#endif
if (sdkBaseEventHandlers.Count > 0)
{
_builder.OnGUIError(scene,
"You have Event Handlers in your scene that are not allowed in this build configuration.",
delegate
{
List<Object> gos = sdkBaseEventHandlers.ConvertAll(item => (Object) item.gameObject);
Selection.objects = gos.ToArray();
},
delegate
{
foreach (VRC_EventHandler eh in sdkBaseEventHandlers)
{
#if VRC_SDK_VRCSDK2
GameObject go = eh.gameObject;
if (isSdk3Scene == false)
{
if (VRC_SceneDescriptor.Instance as VRCSDK2.VRC_SceneDescriptor != null)
go.AddComponent<VRCSDK2.VRC_EventHandler>();
}
#endif
Object.DestroyImmediate(eh);
}
});
}
Vector3 g = Physics.gravity;
if (Math.Abs(g.x) > float.Epsilon || Math.Abs(g.z) > float.Epsilon)
_builder.OnGUIWarning(scene,
"Gravity vector is not straight down. Though we support different gravity, player orientation is always 'upwards' so things don't always behave as you intend.",
delegate { SettingsService.OpenProjectSettings("Project/Physics"); }, null);
if (g.y > 0)
_builder.OnGUIWarning(scene,
"Gravity vector is not straight down, inverted or zero gravity will make walking extremely difficult.",
delegate { SettingsService.OpenProjectSettings("Project/Physics"); }, null);
if (Math.Abs(g.y) < float.Epsilon)
_builder.OnGUIWarning(scene,
"Zero gravity will make walking extremely difficult, though we support different gravity, player orientation is always 'upwards' so this may not have the effect you're looking for.",
delegate { SettingsService.OpenProjectSettings("Project/Physics"); }, null);
if (CheckFogSettings())
{
_builder.OnGUIWarning(
scene,
"Fog shader stripping is set to Custom, this may lead to incorrect or unnecessary shader variants being included in the build. You should use Automatic unless you change the fog mode at runtime.",
delegate { SettingsService.OpenProjectSettings("Project/Graphics"); },
delegate
{
EnvConfig.SetFogSettings(
new EnvConfig.FogSettings(EnvConfig.FogSettings.FogStrippingMode.Automatic));
});
}
if (scene.autoSpatializeAudioSources)
{
_builder.OnGUIWarning(scene,
"Your scene previously used the 'Auto Spatialize Audio Sources' feature. This has been deprecated, press 'Fix' to disable. Also, please add VRC_SpatialAudioSource to all your audio sources. Make sure Spatial Blend is set to 3D for the sources you want to spatialize.",
null,
delegate { scene.autoSpatializeAudioSources = false; }
);
}
AudioSource[] audioSources = Object.FindObjectsOfType<AudioSource>();
foreach (AudioSource a in audioSources)
{
if (a.GetComponent<ONSPAudioSource>() != null)
{
_builder.OnGUIWarning(scene,
"Found audio source(s) using ONSP, this is deprecated. Press 'fix' to convert to VRC_SpatialAudioSource.",
delegate { Selection.activeObject = a.gameObject; },
delegate
{
Selection.activeObject = a.gameObject;
AutoAddSpatialAudioComponents.ConvertONSPAudioSource(a);
}
);
break;
}
else if (a.GetComponent<VRC_SpatialAudioSource>() == null)
{
string msg =
"Found 3D audio source with no VRC Spatial Audio component, this is deprecated. Press 'fix' to add a VRC_SpatialAudioSource.";
if (IsAudioSource2D(a))
msg =
"Found 2D audio source with no VRC Spatial Audio component, this is deprecated. Press 'fix' to add a (disabled) VRC_SpatialAudioSource.";
_builder.OnGUIWarning(scene, msg,
delegate { Selection.activeObject = a.gameObject; },
delegate
{
Selection.activeObject = a.gameObject;
AutoAddSpatialAudioComponents.AddVRCSpatialToBareAudioSource(a);
}
);
break;
}
}
if (VRCSdkControlPanel.HasSubstances())
{
_builder.OnGUIWarning(scene,
"One or more scene objects have Substance materials. This is not supported and may break in game. Please bake your Substances to regular materials.",
() => { Selection.objects = VRCSdkControlPanel.GetSubstanceObjects(); },
null);
}
#if VRC_SDK_VRCSDK2
foreach (VRC_DataStorage ds in Object.FindObjectsOfType<VRC_DataStorage>())
{
VRCSDK2.VRC_ObjectSync os = ds.GetComponent<VRCSDK2.VRC_ObjectSync>();
if (os != null && os.SynchronizePhysics)
_builder.OnGUIWarning(scene, ds.name + " has a VRC_DataStorage and VRC_ObjectSync, with SynchronizePhysics enabled.",
delegate { Selection.activeObject = os.gameObject; }, null);
}
#endif
string vrcFilePath = UnityWebRequest.UnEscapeURL(EditorPrefs.GetString("lastVRCPath"));
if (!string.IsNullOrEmpty(vrcFilePath) &&
ValidationHelpers.CheckIfAssetBundleFileTooLarge(ContentType.World, vrcFilePath, out int fileSize))
{
_builder.OnGUIWarning(scene,
ValidationHelpers.GetAssetBundleOverSizeLimitMessageSDKWarning(ContentType.World, fileSize), null,
null);
}
foreach (GameObject go in Object.FindObjectsOfType<GameObject>())
{
if (go.transform.parent == null)
{
// check root game objects
#if UNITY_ANDROID
IEnumerable<Shader> illegalShaders = VRCSDK2.Validation.WorldValidation.FindIllegalShaders(go);
foreach (Shader s in illegalShaders)
{
_builder.OnGUIWarning(scene, "World uses unsupported shader '" + s.name + "'. This could cause low performance or future compatibility issues.", null, null);
}
#endif
}
else
{
// Check sibling game objects
for (int idx = 0; idx < go.transform.parent.childCount; ++idx)
{
Transform t = go.transform.parent.GetChild(idx);
if (t == go.transform)
continue;
#if VRC_SDK_VRCSDK2
bool allowedType = (t.GetComponent<VRCSDK2.VRC_ObjectSync>()
|| t.GetComponent<VRCSDK2.VRC_SyncAnimation>()
|| t.GetComponent<VRCSDK2.VRC_SyncVideoPlayer>()
|| t.GetComponent<VRCSDK2.VRC_SyncVideoStream>());
if (t.name != go.transform.name || allowedType) continue;
#else
if (t.name != go.transform.name) continue;
#endif
string path = t.name;
Transform p = t.parent;
while (p != null)
{
path = p.name + "/" + path;
p = p.parent;
}
_builder.OnGUIWarning(scene,
"Sibling objects share the same path, which may break network events: " + path,
delegate
{
List<Object> gos = new List<Object>();
for (int c = 0; c < go.transform.parent.childCount; ++c)
if (go.transform.parent.GetChild(c).name == go.name)
gos.Add(go.transform.parent.GetChild(c).gameObject);
Selection.objects = gos.ToArray();
},
delegate
{
List<Object> gos = new List<Object>();
for (int c = 0; c < go.transform.parent.childCount; ++c)
if (go.transform.parent.GetChild(c).name == go.name)
gos.Add(go.transform.parent.GetChild(c).gameObject);
Selection.objects = gos.ToArray();
for (int i = 0; i < gos.Count; ++i)
gos[i].name = gos[i].name + "-" + i.ToString("00");
});
break;
}
}
}
// detect dynamic materials and prefabs with identical names (since these could break triggers)
VRC_Trigger[] triggers = Tools.FindSceneObjectsOfTypeAll<VRC_Trigger>();
List<GameObject> prefabs = new List<GameObject>();
List<Material> materials = new List<Material>();
#if VRC_SDK_VRCSDK2
AssetExporter.FindDynamicContent(ref prefabs, ref materials);
#elif VRC_SDK_VRCSDK3
AssetExporter.FindDynamicContent(ref prefabs, ref materials);
#endif
foreach (VRC_Trigger t in triggers)
{
foreach (VRC_Trigger.TriggerEvent triggerEvent in t.Triggers)
{
foreach (VRC_EventHandler.VrcEvent e in triggerEvent.Events.Where(evt =>
evt.EventType == VRC_EventHandler.VrcEventType.SpawnObject))
{
GameObject go =
AssetDatabase.LoadAssetAtPath(e.ParameterString, typeof(GameObject)) as GameObject;
if (go == null) continue;
foreach (GameObject existing in prefabs)
{
if (go == existing || go.name != existing.name) continue;
_builder.OnGUIWarning(scene,
"Trigger prefab '" + AssetDatabase.GetAssetPath(go).Replace(".prefab", "") +
"' has same name as a prefab in another folder. This may break the trigger.",
delegate { Selection.objects = new Object[] {go}; },
delegate
{
AssetDatabase.RenameAsset(AssetDatabase.GetAssetPath(go),
go.name + "-" + go.GetInstanceID());
AssetDatabase.Refresh();
e.ParameterString = AssetDatabase.GetAssetPath(go);
});
}
}
foreach (VRC_EventHandler.VrcEvent e in triggerEvent.Events.Where(evt =>
evt.EventType == VRC_EventHandler.VrcEventType.SetMaterial))
{
Material mat = AssetDatabase.LoadAssetAtPath<Material>(e.ParameterString);
if (mat == null || mat.name.Contains("(Instance)")) continue;
foreach (Material existing in materials)
{
if (mat == existing || mat.name != existing.name) continue;
_builder.OnGUIWarning(scene,
"Trigger material '" + AssetDatabase.GetAssetPath(mat).Replace(".mat", "") +
"' has same name as a material in another folder. This may break the trigger.",
delegate { Selection.objects = new Object[] {mat}; },
delegate
{
AssetDatabase.RenameAsset(AssetDatabase.GetAssetPath(mat),
mat.name + "-" + mat.GetInstanceID());
AssetDatabase.Refresh();
e.ParameterString = AssetDatabase.GetAssetPath(mat);
});
}
}
}
}
}
private void OnGUISceneSettings(VRC_SceneDescriptor scene)
{
GUILayout.BeginHorizontal();
GUILayout.FlexibleSpace();
GUILayout.BeginVertical(VRCSdkControlPanel.boxGuiStyle, GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth));
string name = "Unpublished VRChat World";
if (scene.apiWorld != null)
name = (scene.apiWorld as Core.ApiWorld)?.name;
EditorGUILayout.Space();
EditorGUILayout.LabelField(name, VRCSdkControlPanel.titleGuiStyle);
Core.PipelineManager[] pms = Tools.FindSceneObjectsOfTypeAll<Core.PipelineManager>();
if (pms.Length == 1)
{
if (!string.IsNullOrEmpty(pms[0].blueprintId))
{
if (scene.apiWorld == null)
{
Core.ApiWorld world = Core.API.FromCacheOrNew<Core.ApiWorld>(pms[0].blueprintId);
world.Fetch(null, null,
(c) => scene.apiWorld = c.Model as Core.ApiWorld,
(c) =>
{
if (c.Code == 404)
{
Core.Logger.Log(
$"Could not load world {pms[0].blueprintId} because it didn't exist.", Core.DebugLevel.All);
Core.ApiCache.Invalidate<Core.ApiWorld>(pms[0].blueprintId);
}
else
Debug.LogErrorFormat("Could not load world {0} because {1}", pms[0].blueprintId,
c.Error);
});
scene.apiWorld = world;
}
}
else
{
// clear scene.apiWorld if blueprint ID has been detached, so world details in builder panel are also cleared
scene.apiWorld = null;
}
}
if (scene.apiWorld != null)
{
Core.ApiWorld w = (scene.apiWorld as Core.ApiWorld);
DrawContentInfoForWorld(w);
VRCSdkControlPanel.DrawContentPlatformSupport(w);
}
VRCSdkControlPanel.DrawBuildTargetSwitcher();
GUILayout.EndVertical();
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
}
void OnGUIScene()
{
GUILayout.Label("", VRCSdkControlPanel.scrollViewSeparatorStyle);
_builderScrollPos = GUILayout.BeginScrollView(_builderScrollPos, false, false, GUIStyle.none,
GUI.skin.verticalScrollbar, GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth),
GUILayout.MinHeight(217));
GUILayout.BeginVertical(VRCSdkControlPanel.boxGuiStyle, GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth));
GUILayout.BeginHorizontal();
GUILayout.BeginVertical(GUILayout.Width(300));
EditorGUILayout.Space();
GUILayout.Label("Local Testing", VRCSdkControlPanel.infoGuiStyle);
GUILayout.Label(
"Before uploading your world you may build and test it in the VRChat client. You won't be able to invite anyone from online but you can launch multiple of your own clients.",
VRCSdkControlPanel.infoGuiStyle);
GUILayout.EndVertical();
GUILayout.BeginVertical(GUILayout.Width(200));
EditorGUILayout.Space();
_numClients = EditorGUILayout.IntField("Number of Clients", _numClients, GUILayout.MaxWidth(190));
EditorGUILayout.Space();
_forceNoVr = EditorGUILayout.Toggle("Force Non-VR", _forceNoVr, GUILayout.MaxWidth(190));
EditorGUILayout.Space();
GUI.enabled = _builder.NoGuiErrorsOrIssues();
string lastUrl = VRC_SdkBuilder.GetLastUrl();
bool lastBuildPresent = lastUrl != null;
if (lastBuildPresent == false)
GUI.enabled = false;
if (VRCSettings.Get().DisplayAdvancedSettings)
{
if (GUILayout.Button("Last Build"))
{
VRC_SdkBuilder.shouldBuildUnityPackage = false;
VRC_SdkBuilder.SetNumClients(_numClients);
VRC_SdkBuilder.forceNoVR = _forceNoVr;
VRC_SdkBuilder.RunLastExportedSceneResource();
}
if (Core.APIUser.CurrentUser.hasSuperPowers)
{
if (GUILayout.Button("Copy Test URL"))
{
TextEditor te = new TextEditor {text = lastUrl};
te.SelectAll();
te.Copy();
}
}
}
GUI.enabled = _builder.NoGuiErrorsOrIssues() ||
Core.APIUser.CurrentUser.developerType == Core.APIUser.DeveloperType.Internal;
#if UNITY_ANDROID
EditorGUI.BeginDisabledGroup(true);
#endif
if (GUILayout.Button("Build & Test"))
{
bool buildTestBlocked = !VRCBuildPipelineCallbacks.OnVRCSDKBuildRequested(VRCSDKRequestedBuildType.Scene);
if (!buildTestBlocked)
{
#if VRC_SDK_VRCSDK2
EnvConfig.ConfigurePlayerSettings();
VRC_SdkBuilder.shouldBuildUnityPackage = false;
AssetExporter.CleanupUnityPackageExport(); // force unity package rebuild on next publish
VRC_SdkBuilder.SetNumClients(_numClients);
VRC_SdkBuilder.forceNoVR = _forceNoVr;
VRC_SdkBuilder.PreBuildBehaviourPackaging();
VRC_SdkBuilder.ExportSceneResourceAndRun();
#elif VRC_SDK_VRCSDK3
EnvConfig.ConfigurePlayerSettings();
VRC_SdkBuilder.shouldBuildUnityPackage = false;
AssetExporter.CleanupUnityPackageExport(); // force unity package rebuild on next publish
VRC_SdkBuilder.SetNumClients(_numClients);
VRC_SdkBuilder.forceNoVR = _forceNoVr;
VRC_SdkBuilder.PreBuildBehaviourPackaging();
VRC_SdkBuilder.ExportSceneResourceAndRun();
#endif
}
}
#if UNITY_ANDROID
EditorGUI.EndDisabledGroup();
#endif
GUILayout.EndVertical();
if (Event.current.type != EventType.Used)
{
GUILayout.EndHorizontal();
EditorGUILayout.Space();
GUILayout.EndVertical();
}
EditorGUILayout.Space();
GUILayout.BeginVertical(VRCSdkControlPanel.boxGuiStyle, GUILayout.Width(VRCSdkControlPanel.SdkWindowWidth));
GUILayout.BeginHorizontal();
GUILayout.BeginVertical(GUILayout.Width(300));
EditorGUILayout.Space();
GUILayout.Label("Online Publishing", VRCSdkControlPanel.infoGuiStyle);
GUILayout.Label(
"In order for other people to enter your world in VRChat it must be built and published to our game servers.",
VRCSdkControlPanel.infoGuiStyle);
EditorGUILayout.Space();
GUILayout.EndVertical();
GUILayout.BeginVertical(GUILayout.Width(200));
EditorGUILayout.Space();
if (lastBuildPresent == false)
GUI.enabled = false;
if (VRCSettings.Get().DisplayAdvancedSettings)
{
if (GUILayout.Button("Last Build"))
{
if (Core.APIUser.CurrentUser.canPublishWorlds)
{
EditorPrefs.SetBool("VRC.SDKBase_StripAllShaders", false);
VRC_SdkBuilder.shouldBuildUnityPackage = VRCSdkControlPanel.FutureProofPublishEnabled;
VRC_SdkBuilder.UploadLastExportedSceneBlueprint();
}
else
{
VRCSdkControlPanel.ShowContentPublishPermissionsDialog();
}
}
}
GUI.enabled = _builder.NoGuiErrorsOrIssues() ||
Core.APIUser.CurrentUser.developerType == Core.APIUser.DeveloperType.Internal;
if (GUILayout.Button(VRCSdkControlPanel.GetBuildAndPublishButtonString()))
{
bool buildBlocked = !VRCBuildPipelineCallbacks.OnVRCSDKBuildRequested(VRCSDKRequestedBuildType.Scene);
if (!buildBlocked)
{
if (Core.APIUser.CurrentUser.canPublishWorlds)
{
EnvConfig.ConfigurePlayerSettings();
EditorPrefs.SetBool("VRC.SDKBase_StripAllShaders", false);
VRC_SdkBuilder.shouldBuildUnityPackage = VRCSdkControlPanel.FutureProofPublishEnabled;
VRC_SdkBuilder.PreBuildBehaviourPackaging();
VRC_SdkBuilder.ExportAndUploadSceneBlueprint();
}
else
{
VRCSdkControlPanel.ShowContentPublishPermissionsDialog();
}
}
}
GUILayout.EndVertical();
GUI.enabled = true;
if (Event.current.type == EventType.Used) return;
GUILayout.EndHorizontal();
GUILayout.EndVertical();
GUILayout.EndScrollView();
}
private static void DrawContentInfoForWorld(Core.ApiWorld w)
{
VRCSdkControlPanel.DrawContentInfo(w.name, w.version.ToString(), w.description, w.capacity.ToString(), w.releaseStatus,
w.tags);
}
private static void CheckUploadChanges(VRC_SceneDescriptor scene)
{
if (!EditorPrefs.HasKey("VRC.SDKBase_scene_changed") ||
!EditorPrefs.GetBool("VRC.SDKBase_scene_changed")) return;
EditorPrefs.DeleteKey("VRC.SDKBase_scene_changed");
if (EditorPrefs.HasKey("VRC.SDKBase_capacity"))
{
scene.capacity = EditorPrefs.GetInt("VRC.SDKBase_capacity");
EditorPrefs.DeleteKey("VRC.SDKBase_capacity");
}
if (EditorPrefs.HasKey("VRC.SDKBase_content_sex"))
{
scene.contentSex = EditorPrefs.GetBool("VRC.SDKBase_content_sex");
EditorPrefs.DeleteKey("VRC.SDKBase_content_sex");
}
if (EditorPrefs.HasKey("VRC.SDKBase_content_violence"))
{
scene.contentViolence = EditorPrefs.GetBool("VRC.SDKBase_content_violence");
EditorPrefs.DeleteKey("VRC.SDKBase_content_violence");
}
if (EditorPrefs.HasKey("VRC.SDKBase_content_gore"))
{
scene.contentGore = EditorPrefs.GetBool("VRC.SDKBase_content_gore");
EditorPrefs.DeleteKey("VRC.SDKBase_content_gore");
}
if (EditorPrefs.HasKey("VRC.SDKBase_content_other"))
{
scene.contentOther = EditorPrefs.GetBool("VRC.SDKBase_content_other");
EditorPrefs.DeleteKey("VRC.SDKBase_content_other");
}
if (EditorPrefs.HasKey("VRC.SDKBase_release_public"))
{
scene.releasePublic = EditorPrefs.GetBool("VRC.SDKBase_release_public");
EditorPrefs.DeleteKey("VRC.SDKBase_release_public");
}
EditorUtility.SetDirty(scene);
EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
}
private static bool CheckFogSettings()
{
EnvConfig.FogSettings fogSettings = EnvConfig.GetFogSettings();
if (fogSettings.fogStrippingMode == EnvConfig.FogSettings.FogStrippingMode.Automatic)
{
return false;
}
return fogSettings.keepLinear || fogSettings.keepExp || fogSettings.keepExp2;
}
private static bool IsAudioSource2D(AudioSource src)
{
AnimationCurve curve = src.GetCustomCurve(AudioSourceCurveType.SpatialBlend);
return Math.Abs(src.spatialBlend) < float.Epsilon && (curve == null || curve.keys.Length <= 1);
}
void OnGUISceneLayer(int layer, string name, string description)
{
if (LayerMask.LayerToName(layer) != name)
_builder.OnGUIError(null, "Layer " + layer + " must be renamed to '" + name + "'",
delegate { SettingsService.OpenProjectSettings("Project/Physics"); }, null);
if (_builder.showLayerHelp)
_builder.OnGUIInformation(null, "Layer " + layer + " " + name + "\n" + description);
}
}
}