test
This commit is contained in:
@@ -0,0 +1,317 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Unity.Multiplayer.Center.Analytics;
|
||||
using Unity.Multiplayer.Center.Common;
|
||||
using Unity.Multiplayer.Center.Onboarding;
|
||||
using Unity.Multiplayer.Center.Questionnaire;
|
||||
using Unity.Multiplayer.Center.Window.UI;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace Unity.Multiplayer.Center.Window
|
||||
{
|
||||
[Serializable]
|
||||
internal class QuickstartCategory
|
||||
{
|
||||
[SerializeField]
|
||||
public OnboardingSectionCategory Category;
|
||||
|
||||
[SerializeReference]
|
||||
public IOnboardingSection[] Sections;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is the main view for the Quickstart tab.
|
||||
/// Note that in the code, the Quickstart tab is referred to as the Getting Started tab.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
internal class GettingStartedTabView : ITabView
|
||||
{
|
||||
const string k_SectionUssClass = "onboarding-section-category-container";
|
||||
const string k_CategoryButtonUssClass = "onboarding-category-button";
|
||||
const string k_OnboardingCategoriesUssClass = "onboarding-categories";
|
||||
const string k_OnboardingContentUssClass = "onboarding-content";
|
||||
|
||||
[field: SerializeField]
|
||||
public string Name { get; private set; }
|
||||
|
||||
public bool IsEnabled => PackageManagement.IsAnyMultiplayerPackageInstalled();
|
||||
|
||||
public string ToolTip => IsEnabled ? "" : "Please install some multiplayer packages to access quickstart content.";
|
||||
|
||||
public VisualElement RootVisualElement { get; set; }
|
||||
|
||||
[SerializeField]
|
||||
int m_SelectedCategory;
|
||||
|
||||
Dictionary<OnboardingSectionCategory, int> m_CategoryIndices;
|
||||
|
||||
VisualElement[] m_CategoryContainers;
|
||||
|
||||
[SerializeField]
|
||||
QuickstartCategory[] m_SectionCategories;
|
||||
/// <summary>
|
||||
/// To find out if new section appeared, we need to keep track of the last section types we found.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
AvailableSectionTypes m_LastFoundSectionTypes;
|
||||
|
||||
public IMultiplayerCenterAnalytics MultiplayerCenterAnalytics { get; set; }
|
||||
|
||||
public GettingStartedTabView(string name = "Quickstart")
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
Debug.Assert(MultiplayerCenterAnalytics != null, "MultiplayerCenterAnalytics != null");
|
||||
UserChoicesObject.instance.OnSolutionSelectionChanged -= NotifyChoicesChanged;
|
||||
UserChoicesObject.instance.OnSolutionSelectionChanged += NotifyChoicesChanged;
|
||||
|
||||
var currentSectionTypes = SectionsFinder.FindSectionTypes();
|
||||
|
||||
if (m_SectionCategories == null || m_SectionCategories.Length == 0 || m_LastFoundSectionTypes.HaveTypesChanged(currentSectionTypes))
|
||||
{
|
||||
m_LastFoundSectionTypes = currentSectionTypes;
|
||||
ConstructSectionInstances();
|
||||
CreateViews();
|
||||
}
|
||||
else if(RootVisualElement == null || RootVisualElement.childCount == 0)
|
||||
{
|
||||
CreateViews();
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
RootVisualElement?.Clear();
|
||||
if (m_SectionCategories == null)
|
||||
return;
|
||||
foreach (var category in m_SectionCategories)
|
||||
{
|
||||
if(category == null) continue;
|
||||
foreach (var section in category.Sections)
|
||||
{
|
||||
section?.Unload();
|
||||
}
|
||||
}
|
||||
|
||||
Array.Clear(m_SectionCategories, 0, m_SectionCategories.Length);
|
||||
}
|
||||
|
||||
public void SetVisible(bool visible)
|
||||
{
|
||||
RootVisualElement.style.display = visible ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
|
||||
void ConstructSectionInstances()
|
||||
{
|
||||
var enumValues = Enum.GetValues(typeof(OnboardingSectionCategory));
|
||||
var allCategories = new QuickstartCategory[enumValues.Length];
|
||||
foreach (var categoryObject in enumValues)
|
||||
{
|
||||
var category = (OnboardingSectionCategory) categoryObject;
|
||||
var categoryData = new QuickstartCategory {Category = category, Sections = Array.Empty<IOnboardingSection>()};
|
||||
allCategories[(int) category] = categoryData;
|
||||
if (!m_LastFoundSectionTypes.TryGetValue(category, out var sectionTypes))
|
||||
{
|
||||
continue; // no section for that category
|
||||
}
|
||||
|
||||
categoryData.Sections = new IOnboardingSection[sectionTypes.Length];
|
||||
for (var index = 0; index < sectionTypes.Length; index++)
|
||||
{
|
||||
var sectionType = sectionTypes[index];
|
||||
var newSection = SectionFromType(sectionType);
|
||||
|
||||
// TODO: check what to do with null sections
|
||||
if (newSection == null) continue;
|
||||
|
||||
categoryData.Sections[index] = newSection;
|
||||
}
|
||||
}
|
||||
|
||||
m_SectionCategories = allCategories;
|
||||
}
|
||||
|
||||
void SetSelectedCategory(int categoryIndex)
|
||||
{
|
||||
m_SelectedCategory = categoryIndex;
|
||||
for (var index = 0; index < m_CategoryContainers.Length; index++)
|
||||
{
|
||||
var categoryContainer = m_CategoryContainers[index];
|
||||
if(categoryContainer != null)
|
||||
categoryContainer.style.display = index == categoryIndex ? DisplayStyle.Flex : DisplayStyle.None;
|
||||
}
|
||||
}
|
||||
|
||||
void CreateViews()
|
||||
{
|
||||
RootVisualElement ??= new VisualElement();
|
||||
RootVisualElement.Clear();
|
||||
|
||||
if (QuickstartIsMissingView.ShouldShow)
|
||||
{
|
||||
RootVisualElement.Add(new QuickstartIsMissingView().RootVisualElement);
|
||||
}
|
||||
|
||||
m_CategoryIndices = new Dictionary<OnboardingSectionCategory, int>();
|
||||
m_CategoryContainers = new VisualElement[m_SectionCategories.Length];
|
||||
|
||||
var horizontalContainer = new TwoPaneSplitView(0, 250, TwoPaneSplitViewOrientation.Horizontal);
|
||||
RootVisualElement.Add(horizontalContainer);
|
||||
horizontalContainer.AddToClassList(StyleClasses.MainSplitView);
|
||||
var buttonGroup = new ToggleButtonGroup() { allowEmptySelection = false, isMultipleSelection = false};
|
||||
buttonGroup.AddToClassList(k_OnboardingCategoriesUssClass);
|
||||
buttonGroup.AddToClassList(StyleClasses.MainSplitViewLeft);
|
||||
horizontalContainer.Add(buttonGroup);
|
||||
|
||||
var scrollView = new ScrollView(ScrollViewMode.Vertical) {horizontalScrollerVisibility = ScrollerVisibility.Hidden};
|
||||
scrollView.AddToClassList(StyleClasses.MainSplitViewRight);
|
||||
scrollView.AddToClassList(k_OnboardingContentUssClass);
|
||||
|
||||
horizontalContainer.Add(scrollView);
|
||||
|
||||
var index = -1;
|
||||
foreach (var categoryData in m_SectionCategories)
|
||||
{
|
||||
if (categoryData == null || categoryData.Sections.Length == 0) continue;
|
||||
|
||||
++index;
|
||||
var category = categoryData.Category;
|
||||
var currentContainer = StartNewSection(scrollView, category);
|
||||
scrollView.Add(currentContainer);
|
||||
|
||||
m_CategoryIndices[category] = index;
|
||||
m_CategoryContainers[index] = currentContainer;
|
||||
|
||||
var button = new Button { text = SectionCategoryToString(category)};
|
||||
button.AddToClassList(k_CategoryButtonUssClass);
|
||||
buttonGroup.Add(button);
|
||||
|
||||
CreateSectionViewsIn(currentContainer, categoryData);
|
||||
}
|
||||
|
||||
// Hide the SplitView if we have nothing to show
|
||||
var noContentToShow = index == -1;
|
||||
horizontalContainer.style.display = noContentToShow ? DisplayStyle.None : DisplayStyle.Flex;
|
||||
|
||||
if (noContentToShow && !QuickstartIsMissingView.ShouldShow)
|
||||
{
|
||||
var noContentLabel = new Label("No content is available for the current selection in Netcode Solution and Hosting Model.");
|
||||
noContentLabel.style.marginLeft = noContentLabel.style.marginRight = noContentLabel.style.marginTop = noContentLabel.style.marginBottom = 8;
|
||||
RootVisualElement.Add(noContentLabel);
|
||||
}
|
||||
|
||||
SetSelectedCategory(m_SelectedCategory);
|
||||
ulong mask = (ulong) 1 << m_SelectedCategory;
|
||||
buttonGroup.SetValueWithoutNotify(new ToggleButtonGroupState(mask, m_CategoryIndices.Count));
|
||||
|
||||
// MTT-8918 Block the callback on register as it will always return index 0,
|
||||
// which can result in a mismatch between toggle group and selected category.
|
||||
var onCreateFrame = EditorApplication.timeSinceStartup;
|
||||
buttonGroup.RegisterValueChangedCallback(evt =>
|
||||
{
|
||||
if (Math.Abs(onCreateFrame - EditorApplication.timeSinceStartup) < 0.05f)
|
||||
return;
|
||||
|
||||
var selectedIndex = evt.newValue.GetActiveOptions(stackalloc int[evt.newValue.length])[0];
|
||||
SetSelectedCategory(selectedIndex);
|
||||
});
|
||||
NotifyChoicesChanged();
|
||||
}
|
||||
|
||||
void CreateSectionViewsIn(VisualElement currentContainer, QuickstartCategory categoryData)
|
||||
{
|
||||
foreach (var section in categoryData.Sections)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (section is ISectionWithAnalytics sectionWithAnalytics)
|
||||
{
|
||||
var attribute = section.GetType().GetCustomAttribute<OnboardingSectionAttribute>();
|
||||
sectionWithAnalytics.AnalyticsProvider = new OnboardingSectionAnalyticsProvider(MultiplayerCenterAnalytics,
|
||||
targetPackageId: attribute.TargetPackageId, sectionId: attribute.Id);
|
||||
}
|
||||
|
||||
section.Load();
|
||||
section.Root.name = section.GetType().Name;
|
||||
currentContainer.Add(section.Root);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning($"Could not load onboarding section {section?.GetType()}: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NotifyChoicesChanged()
|
||||
{
|
||||
if (m_SectionCategories == null)
|
||||
return;
|
||||
|
||||
foreach (var category in m_SectionCategories)
|
||||
{
|
||||
if (category == null) continue;
|
||||
foreach (var section in category.Sections)
|
||||
{
|
||||
if (section is not ISectionDependingOnUserChoices dependentSection) continue;
|
||||
|
||||
try
|
||||
{
|
||||
dependentSection.HandleAnswerData(UserChoicesObject.instance.UserAnswers);
|
||||
dependentSection.HandlePreset(UserChoicesObject.instance.Preset);
|
||||
dependentSection.HandleUserSelectionData(UserChoicesObject.instance.SelectedSolutions);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogWarning($"Could not set data for onboarding section {section.GetType()}: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static VisualElement StartNewSection(VisualElement parent, OnboardingSectionCategory category)
|
||||
{
|
||||
var container = new VisualElement();
|
||||
if (category != OnboardingSectionCategory.Intro)
|
||||
{
|
||||
var titleContainer = new VisualElement();
|
||||
titleContainer.AddToClassList(StyleClasses.ViewHeadline);
|
||||
|
||||
var title = new Label(SectionCategoryToString(category));
|
||||
titleContainer.Add(title);
|
||||
container.Add(titleContainer);
|
||||
}
|
||||
|
||||
container.AddToClassList(k_SectionUssClass);
|
||||
parent.Add(container);
|
||||
return container;
|
||||
}
|
||||
|
||||
static IOnboardingSection SectionFromType(Type type)
|
||||
{
|
||||
var constructed = type.GetConstructor(Type.EmptyTypes)?.Invoke(null);
|
||||
if (constructed is IOnboardingSection section) return section;
|
||||
|
||||
Debug.LogWarning($"Could not create onboarding section {type}");
|
||||
return null;
|
||||
}
|
||||
|
||||
static string SectionCategoryToString(OnboardingSectionCategory category)
|
||||
{
|
||||
return category switch
|
||||
{
|
||||
OnboardingSectionCategory.Intro => "Intro",
|
||||
OnboardingSectionCategory.Netcode => "Netcode and Tools",
|
||||
OnboardingSectionCategory.ConnectingPlayers => "Connecting Players",
|
||||
OnboardingSectionCategory.ServerInfrastructure => "Hosting",
|
||||
OnboardingSectionCategory.Other => "Other",
|
||||
_ => category.ToString()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d2c2caab1f2642afa6c07dec054fd34f
|
||||
timeCreated: 1700578915
|
@@ -0,0 +1,40 @@
|
||||
using UnityEngine.UIElements;
|
||||
namespace Unity.Multiplayer.Center.Onboarding
|
||||
{
|
||||
/// <summary>
|
||||
/// A lot of the getting started content is in the package com.unity.multiplayer.center.quickstart
|
||||
/// This class is responsible for handling the package and its installation
|
||||
/// </summary>
|
||||
internal class QuickstartIsMissingView
|
||||
{
|
||||
public const string PackageId = "com.unity.multiplayer.center.quickstart";
|
||||
|
||||
Button m_Button;
|
||||
public VisualElement RootVisualElement { get; private set; }
|
||||
|
||||
public static bool ShouldShow => !PackageManagement.IsInstalled(PackageId);
|
||||
|
||||
public QuickstartIsMissingView()
|
||||
{
|
||||
RootVisualElement = new HelpBox("The Quickstart package is not installed, so not all the content will be available in this view.", HelpBoxMessageType.Warning);
|
||||
m_Button = new Button(InstallQuickstart) {text = "Install"};
|
||||
RootVisualElement.style.marginLeft = RootVisualElement.style.marginRight = RootVisualElement.style.marginTop = RootVisualElement.style.marginBottom = 8;
|
||||
RootVisualElement.Add(m_Button);
|
||||
}
|
||||
|
||||
void InstallQuickstart()
|
||||
{
|
||||
PackageManagement.InstallPackage(PackageId, OnInstallFinished);
|
||||
}
|
||||
|
||||
void OnInstallFinished(bool success)
|
||||
{
|
||||
if (!success)
|
||||
return;
|
||||
|
||||
if(m_Button != null)
|
||||
m_Button.clicked -= InstallQuickstart;
|
||||
RootVisualElement?.RemoveFromHierarchy();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 322b0121d39f45208a3d933e6926a9d8
|
||||
timeCreated: 1713262637
|
@@ -0,0 +1,188 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Unity.Multiplayer.Center.Common;
|
||||
using Unity.Multiplayer.Center.Questionnaire;
|
||||
using Unity.Multiplayer.Center.Recommendations;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using DisplayCondition = Unity.Multiplayer.Center.Common.DisplayCondition;
|
||||
|
||||
namespace Unity.Multiplayer.Center.Onboarding
|
||||
{
|
||||
using SectionCategoryToSectionIdToSectionType = Dictionary<OnboardingSectionCategory, Dictionary<string, Type>>;
|
||||
|
||||
using SectionCategoryToSectionList = Dictionary<OnboardingSectionCategory, Type[]>;
|
||||
|
||||
/// <summary>
|
||||
/// Stores the available section types in a serializable way, but for comparison purposes only.
|
||||
/// Only the assembly qualified names are serialized in a sorted array.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
internal class AvailableSectionTypes
|
||||
{
|
||||
readonly SectionCategoryToSectionList m_SectionMapping;
|
||||
|
||||
[SerializeField]
|
||||
string[] m_SectionTypeNames;
|
||||
|
||||
public IEnumerable<string> SectionTypeNames => m_SectionTypeNames;
|
||||
|
||||
public AvailableSectionTypes(SectionCategoryToSectionList sectionTypes)
|
||||
{
|
||||
m_SectionMapping = sectionTypes;
|
||||
m_SectionTypeNames = RecommendationUtils.GetSectionTypeNamesInOrder(m_SectionMapping).ToArray();
|
||||
}
|
||||
|
||||
public bool TryGetValue(OnboardingSectionCategory category, out Type[] sectionTypes)
|
||||
{
|
||||
return m_SectionMapping.TryGetValue(category, out sectionTypes);
|
||||
}
|
||||
|
||||
public bool HaveTypesChanged(AvailableSectionTypes other)
|
||||
{
|
||||
return m_SectionTypeNames == null || !RecommendationUtils.AreArraysEqual(m_SectionTypeNames, other.m_SectionTypeNames);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class SectionsFinder
|
||||
{
|
||||
/// <summary>
|
||||
/// The target packages that are used to determine if "nothing is installed", which results in sections with
|
||||
/// the display condition "NoPackageInstalled" to be displayed.
|
||||
/// </summary>
|
||||
static readonly string[] k_TargetPackages = {
|
||||
"com.unity.services.core", // This is a dependency of all the services, so if any service is installed, this is installed
|
||||
"com.unity.netcode.gameobjects", "com.unity.netcode", // Netcodes
|
||||
};
|
||||
|
||||
public static AvailableSectionTypes FindSectionTypes()
|
||||
{
|
||||
var dico = GetSectionCategoryToSectionIdToSectionType();
|
||||
return SortSectionsByOrder(dico);
|
||||
}
|
||||
|
||||
static AvailableSectionTypes SortSectionsByOrder(SectionCategoryToSectionIdToSectionType dico)
|
||||
{
|
||||
var result = new SectionCategoryToSectionList();
|
||||
foreach (var (category, idToTypeDictionary) in dico)
|
||||
{
|
||||
var typeAttributeList = GetOnboardingTypeAttributeList(idToTypeDictionary);
|
||||
typeAttributeList.Sort((typeA, typeB) => typeA.Attribute.Order.CompareTo(typeB.Attribute.Order));
|
||||
result[category] = GetOnboardingTypeArray(typeAttributeList);
|
||||
}
|
||||
|
||||
return new AvailableSectionTypes(result);
|
||||
}
|
||||
|
||||
static Type[] GetOnboardingTypeArray(List<(Type Type, OnboardingSectionAttribute Attribute)> typeAttributeList)
|
||||
{
|
||||
var typeArray = new Type[typeAttributeList.Count];
|
||||
for (var i = 0; i < typeAttributeList.Count; i++)
|
||||
{
|
||||
typeArray[i] = typeAttributeList[i].Type;
|
||||
}
|
||||
|
||||
return typeArray;
|
||||
}
|
||||
|
||||
static List<(Type Type, OnboardingSectionAttribute Attribute)> GetOnboardingTypeAttributeList(Dictionary<string, Type> idToTypeDictionary)
|
||||
{
|
||||
var typeAttributeList = new List<(Type Type, OnboardingSectionAttribute Attribute)>(idToTypeDictionary.Count);
|
||||
|
||||
foreach (var (_, type) in idToTypeDictionary)
|
||||
{
|
||||
var attribute = type.GetCustomAttribute<OnboardingSectionAttribute>();
|
||||
if (attribute != null)
|
||||
{
|
||||
typeAttributeList.Add((type, attribute));
|
||||
}
|
||||
}
|
||||
|
||||
return typeAttributeList;
|
||||
}
|
||||
|
||||
static SectionCategoryToSectionIdToSectionType GetSectionCategoryToSectionIdToSectionType()
|
||||
{
|
||||
var dico = new Dictionary<OnboardingSectionCategory, Dictionary<string, System.Type>>();
|
||||
foreach (var sectionType in TypeCache.GetTypesDerivedFrom<IOnboardingSection>())
|
||||
{
|
||||
if (sectionType.IsAbstract) continue;
|
||||
|
||||
// check if type has default constructor
|
||||
if (sectionType.GetConstructor(System.Type.EmptyTypes) == null)
|
||||
{
|
||||
Debug.LogWarning($"Onboarding section type {sectionType} does not have a default constructor and will be ignored.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var sectionAttribute = sectionType.GetCustomAttribute<OnboardingSectionAttribute>();
|
||||
if(sectionAttribute == null)
|
||||
continue;
|
||||
|
||||
if (!IsDisplayConditionFulfilledForSection(sectionAttribute))
|
||||
continue;
|
||||
|
||||
if (!IsSectionSupportedBySelectedInfrastructure(sectionAttribute))
|
||||
continue;
|
||||
|
||||
if (!IsSectionSupportedBySelectedNetcodeChoice(sectionAttribute))
|
||||
continue;
|
||||
|
||||
if (!dico.ContainsKey(sectionAttribute.Category))
|
||||
dico[sectionAttribute.Category] = new Dictionary<string, System.Type>();
|
||||
|
||||
if (dico[sectionAttribute.Category].TryGetValue(sectionAttribute.Id, out var existing))
|
||||
{
|
||||
var existingAttr = existing.GetCustomAttribute<OnboardingSectionAttribute>();
|
||||
|
||||
if (existingAttr!= null && existingAttr.Id == sectionAttribute.Id
|
||||
&& sectionAttribute.Priority > existingAttr.Priority)
|
||||
{
|
||||
dico[sectionAttribute.Category][sectionAttribute.Id] = sectionType;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
dico[sectionAttribute.Category][sectionAttribute.Id] = sectionType;
|
||||
}
|
||||
}
|
||||
|
||||
return dico;
|
||||
}
|
||||
|
||||
static bool IsDisplayConditionFulfilledForSection(OnboardingSectionAttribute attribute)
|
||||
{
|
||||
return attribute.DisplayCondition switch
|
||||
{
|
||||
DisplayCondition.None => true,
|
||||
DisplayCondition.PackageInstalled when string.IsNullOrEmpty(attribute.TargetPackageId)
|
||||
=> throw new ArgumentException("PackageInstalled condition requires a target package id"),
|
||||
DisplayCondition.PackageInstalled => PackageManagement.IsInstalled(attribute.TargetPackageId),
|
||||
DisplayCondition.NoPackageInstalled => NothingInstalled(),
|
||||
_ => throw new NotImplementedException($"Unknown display condition: {attribute.DisplayCondition}")
|
||||
};
|
||||
}
|
||||
|
||||
static bool IsSectionSupportedBySelectedInfrastructure(OnboardingSectionAttribute attribute)
|
||||
{
|
||||
if (UserChoicesObject.instance.SelectedSolutions == null) return true;
|
||||
|
||||
var selectedInfrastructure = UserChoicesObject.instance.SelectedSolutions.SelectedHostingModel;
|
||||
return attribute.HostingModelDependency == SelectedSolutionsData.HostingModel.None || attribute.HostingModelDependency == selectedInfrastructure;
|
||||
}
|
||||
|
||||
static bool IsSectionSupportedBySelectedNetcodeChoice(OnboardingSectionAttribute attribute)
|
||||
{
|
||||
if (UserChoicesObject.instance.SelectedSolutions == null) return true;
|
||||
|
||||
var selectedNetcode = UserChoicesObject.instance.SelectedSolutions.SelectedNetcodeSolution;
|
||||
return attribute.NetcodeDependency == SelectedSolutionsData.NetcodeSolution.None || attribute.NetcodeDependency == selectedNetcode;
|
||||
}
|
||||
|
||||
static bool NothingInstalled()
|
||||
{
|
||||
return !PackageManagement.IsAnyPackageInstalled(k_TargetPackages);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fcbb62ff857b4cf49e74f5f9aca6e8d6
|
||||
timeCreated: 1700734062
|
Reference in New Issue
Block a user