This commit is contained in:
2025-01-17 13:10:42 +01:00
commit 4536213c91
15115 changed files with 1442174 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 21f32d2f4add49b3b11fadb6889a2156
timeCreated: 1714382287

View File

@@ -0,0 +1,151 @@
using System;
using Unity.Multiplayer.Center.Common.Analytics;
using UnityEngine.Analytics;
namespace Unity.Multiplayer.Center.Analytics
{
/// <summary>
/// Package representation in the analytics data.
/// </summary>
[Serializable]
internal struct Package
{
/// <summary>
/// The identifier of the package.
/// </summary>
public string PackageId;
/// <summary>
/// Whether the user has selected this package for installation.
/// </summary>
public bool SelectedForInstall;
/// <summary>
/// Whether the package was recommended.
/// </summary>
public bool IsRecommended;
/// <summary>
/// Whether the package was already installed when the installation attempt event occured
/// </summary>
public bool IsAlreadyInstalled;
}
/// <summary>
/// A single Answer to the GameSpecs questionnaire.
/// </summary>
[Serializable]
internal struct GameSpec
{
/// <summary>
/// The identifier of the answered question (does not change).
/// </summary>
public string QuestionId;
/// <summary>
/// The text of the question as displayed in the UI (may change with versions).
/// </summary>
public string QuestionText;
/// <summary>
/// Whether the question accepts multiple answers.
/// </summary>
public bool AcceptsMultipleAnswers;
/// <summary>
/// The identifier of the answered question (does not change).
/// </summary>
public string AnswerId;
/// <summary>
/// The text of the answer as displayed in the UI (may change with versions).
/// </summary>
public string AnswerText;
}
/// <summary>
///
/// </summary>
[Serializable]
internal struct RecommendationData : IAnalytic.IData
{
/// <summary>
/// The preset selected by the user.
/// </summary>
public int Preset;
/// <summary>
/// The preset selected by the user (game genre) as displayed in the UI.
/// </summary>
public string PresetName;
/// <summary>
/// The version defined in the Questionnaire data.
/// </summary>
public string QuestionnaireVersion;
/// <summary>
/// All the selected answers to the questions of the game specs questionnaire.
/// </summary>
public GameSpec[] GameSpecs;
}
/// <summary>
/// What type of content the user Interacted with (buttons).
/// </summary>
[Serializable]
internal struct InteractionData : IAnalytic.IData
{
/// <summary>
/// The identifier of the section that contains the button.
/// </summary>
public string SectionId;
/// <summary>
/// Whether it is a call to action or a link.
/// </summary>
public InteractionDataType Type;
/// <summary>
/// The name of the button in the UI.
/// </summary>
public string DisplayName;
/// <summary>
/// The target package for which the section is helpful.
/// </summary>
public string TargetPackageId;
}
/// <summary>
/// Payload of the installation event.
/// </summary>
[Serializable]
internal struct InstallData : IAnalytic.IData
{
/// <summary>
/// The preset selected by the user.
/// </summary>
public int Preset;
/// <summary>
/// The preset selected by the user (game genre) as displayed in the UI.
/// </summary>
public string PresetName;
/// <summary>
/// The version defined in the Questionnaire data.
/// </summary>
public string QuestionnaireVersion;
/// <summary>
/// All the selected answers to the questions of the game specs questionnaire.
/// </summary>
public GameSpec[] GamesSpecs;
/// <summary>
/// The packages that were in the recommendation tab of the multiplayer center
/// </summary>
public Package[] Packages;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4cee1dc929764056ac40ece91efef712
timeCreated: 1714481696

View File

@@ -0,0 +1,175 @@
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 Unity.Multiplayer.Center.Window.UI;
using UnityEngine;
namespace Unity.Multiplayer.Center.Analytics
{
internal static class AnalyticsUtils
{
// hard-coded to avoid recomputing every time / resizing arrays
public const int NumNetcodePackage = 2;
public const int NumHostingPackages = 1;
/// <summary>
/// From the recommendation view data (which contains the packages that the user sees and the user's selection),
/// create the list of packages that will be sent to the analytics backend.
/// </summary>
/// <param name="data">The recommendation view data as shown in the recommendation tab</param>
/// <param name="solutionToPackageData">The packages views</param>
/// <returns>The list of packages to be sent along with the installation event.</returns>
public static Package[] GetPackagesWithAnalyticsFormat(RecommendationViewData data, SolutionsToRecommendedPackageViewData solutionToPackageData)
{
var selectedNetcode = RecommendationUtils.GetSelectedNetcode(data);
var selectedHostingModel = RecommendationUtils.GetSelectedHostingModel(data);
var packages = solutionToPackageData.GetPackagesForSelection(selectedNetcode.Solution, selectedHostingModel.Solution);
var packageCount = NumNetcodePackage + NumHostingPackages + packages.Length;
var result = new Package[packageCount];
var resultIndex = 0;
AddSolutionPackages(data.NetcodeOptions, result, ref resultIndex);
AddSolutionPackages(data.ServerArchitectureOptions, result, ref resultIndex);
AddRecommendedPackages(packages, result, ref resultIndex);
Debug.Assert(resultIndex == packageCount, $"Expected {packageCount} packages, got {resultIndex}");
return result;
}
/// <summary>
/// Fetches all the inspector name attributes of the Preset enum and returns the displayNames
/// Important! It assumes the enum values are 0, ... , N
/// </summary>
/// <returns>The array of preset names. The index in the array is the integer value of the enum value</returns>
public static string[] GetPresetFullNames()
{
var t = typeof(Preset);
var values = Enum.GetValues(t);
var array = new string[values.Length];
foreach (var value in values)
{
var preset = (Preset) value;
var index = (int)preset;
var asString = value.ToString();
var memInfo = t.GetMember(asString);
var attribute = memInfo[0].GetCustomAttribute<InspectorNameAttribute>(false);
if (attribute != null)
{
array[index] = attribute.displayName;
}
else
{
Debug.LogError($"Could not fetch the full name of the preset value {asString}");
array[index] = asString;
}
}
return array;
}
/// <summary>
/// Converts AnswerData to game specs, providing the knowledge of the display names.
/// It assumes there is exactly one answer in the answer list at this point.
/// </summary>
/// <param name="data">The answer data of the user</param>
/// <param name="answerIdToAnswerName">Mapping answer id to display name</param>
/// <param name="questionIdToQuestionName">Mapping question id to display name</param>
/// <returns>The list of game spec that will be consumed by the analytics backend</returns>
public static GameSpec[] ToGameSpecs(AnswerData data,
IReadOnlyDictionary<string, string> answerIdToAnswerName,
IReadOnlyDictionary<string, string> questionIdToQuestionName)
{
var result = new GameSpec[data.Answers.Count];
for (var i = 0; i < result.Length; ++i)
{
var answer = data.Answers[i];
var answerId = answer.Answers[0]; // TODO: make sure that this always exists
result[i] = new GameSpec()
{
QuestionId = answer.QuestionId,
QuestionText = questionIdToQuestionName[answer.QuestionId],
AcceptsMultipleAnswers = false, // TODO: add test that verifies this assumption
AnswerId = answerId,
AnswerText = answerIdToAnswerName[answerId]
};
}
return result;
}
/// <summary>
/// Creates the mapping from question id to question display name
/// </summary>
/// <param name="questionnaireData">The questionnaire data</param>
/// <returns>The mapping</returns>
public static IReadOnlyDictionary<string, string> GetQuestionDisplayNames(QuestionnaireData questionnaireData)
{
var dictionary = new Dictionary<string, string>();
foreach (var question in questionnaireData.Questions)
{
dictionary[question.Id] = question.Title;
}
return dictionary;
}
/// <summary>
/// Creates the mapping from answer id to answer display name
/// </summary>
/// <param name="questionnaireData">The questionnaire data</param>
/// <returns>The mapping</returns>
public static IReadOnlyDictionary<string, string> GetAnswerDisplayNames(QuestionnaireData questionnaireData)
{
var dictionary = new Dictionary<string, string>();
foreach (var question in questionnaireData.Questions)
{
foreach (var answer in question.Choices)
{
dictionary[answer.Id] = answer.Title;
}
}
return dictionary;
}
static void AddSolutionPackages(RecommendedSolutionViewData[] options, Package[] result, ref int resultIndex)
{
foreach (var t in options)
{
if(string.IsNullOrEmpty(t.MainPackage?.PackageId))
continue;
result[resultIndex] = new Package()
{
PackageId = t.MainPackage.PackageId,
SelectedForInstall = t.Selected && t.RecommendationType != RecommendationType.Incompatible,
IsRecommended = t.RecommendationType is RecommendationType.MainArchitectureChoice,
IsAlreadyInstalled = t.MainPackage.IsInstalledAsProjectDependency
};
++resultIndex;
}
}
static void AddRecommendedPackages(RecommendedPackageViewData[] packageViewDatas, Package[] result, ref int resultIndex)
{
foreach (var viewData in packageViewDatas)
{
result[resultIndex] = new Package()
{
PackageId = viewData.PackageId,
// TODO: remove hidden?
SelectedForInstall = viewData.Selected && viewData.RecommendationType != RecommendationType.Incompatible,
IsRecommended = viewData.RecommendationType.IsRecommendedPackage(),
IsAlreadyInstalled = viewData.IsInstalledAsProjectDependency
};
++resultIndex;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 54407f5deee4439eb1885bee01956e9c
timeCreated: 1714483618

View File

@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Analytics;
namespace Unity.Multiplayer.Center.Analytics
{
/// <summary>
/// Does the same as the MultiplayerCenterAnalytics, but logs the events to the console instead of sending them.
/// It is useful to debug fast, without the EditorAnalytics Debugger package (but it does not replace it).
/// </summary>
internal class DebugAnalytics : MultiplayerCenterAnalytics
{
public DebugAnalytics(string questionnaireVersion, IReadOnlyDictionary<string, string> questionDisplayNames,
IReadOnlyDictionary<string,string> answerDisplayNames)
: base(questionnaireVersion, questionDisplayNames, answerDisplayNames) { }
protected override void SendAnalytic(IAnalytic analytic)
{
analytic.TryGatherData(out var data, out var _);
switch (data)
{
case InstallData installData:
Debug.Log($"Event: {analytic.GetType()} - Data: {ToString(installData)}");
break;
case RecommendationData recommendationData:
Debug.Log($"Event: {analytic.GetType()} - Data: {ToString(recommendationData)}");
break;
case InteractionData interactionEventAnalytic:
Debug.Log($"Event: {analytic.GetType()} - Data: {ToString(interactionEventAnalytic)}");
break;
default:
Debug.Log($"Unknown event: {analytic.GetType()} - Data: {data}");
break;
}
}
static string ToString(GameSpec p) => $"GameSpec [{p.QuestionText} -> {p.AnswerText}]";
static string ToString(Package p) => $"Package [{p.PackageId} - Selected {p.SelectedForInstall} - Reco {p.IsRecommended} - Inst {p.IsAlreadyInstalled}]";
static string ToString(InstallData data)
{
var packageStrings = new List<string>(data.Packages.Length);
foreach (var package in data.Packages)
{
packageStrings.Add(ToString(package));
}
return $"{data.PresetName} - Packages [{data.Packages.Length}] packages: \n{string.Join("\n", packageStrings)}";
}
static string ToString(RecommendationData data)
{
var gameSpecStrings = new List<string>(data.GameSpecs.Length);
foreach (var gameSpec in data.GameSpecs)
{
gameSpecStrings.Add(ToString(gameSpec));
}
return $"{data.PresetName} - GameSpecs [{data.GameSpecs.Length}] gamespecs: \n{string.Join("\n", gameSpecStrings)}";
}
static string ToString(InteractionData data) => $"{data.SectionId}({data.TargetPackageId}) - {data.Type} - {data.DisplayName}";
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: dd20f32039084a12a8e48dffd4d58f48
timeCreated: 1714651117

View File

@@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using Unity.Multiplayer.Center.Common;
using Unity.Multiplayer.Center.Common.Analytics;
using UnityEditor;
using UnityEngine;
using UnityEngine.Analytics;
namespace Unity.Multiplayer.Center.Analytics
{
/// <summary>
/// The interface for the Multiplayer Center Analytics provider (only one functional implementation, but the
/// interface is needed for testing purposes)
/// </summary>
internal interface IMultiplayerCenterAnalytics
{
void SendInstallationEvent(AnswerData data, Preset preset, Package[] packages);
void SendRecommendationEvent(AnswerData data, Preset preset);
void SendGettingStartedInteractionEvent(string targetPackageId, string sectionId, InteractionDataType type, string displayName);
}
/// <summary>
/// The concrete implementation of the multiplayer center analytics provider.
/// It convert
/// </summary>
internal class MultiplayerCenterAnalytics : IMultiplayerCenterAnalytics
{
const string k_VendorKey = "unity.multiplayer.center";
const string k_InstallationEventName = "multiplayer_center_onInstallClicked";
const string k_RecommendationEventName = "multiplayer_center_onRecommendation";
const string k_GetStartedInteractionEventName = "multiplayer_center_onGetStartedInteraction";
readonly string m_QuestionnaireVersion;
readonly IReadOnlyDictionary<string, string> m_AnswerIdToAnswerName;
readonly IReadOnlyDictionary<string, string> m_QuestionIdToQuestionName;
readonly string[] m_PresetFullNames = AnalyticsUtils.GetPresetFullNames();
string PresetName(Preset v) => m_PresetFullNames[(int)v];
GameSpec[] FillGameSpecs(AnswerData data)
{
return AnalyticsUtils.ToGameSpecs(data, m_AnswerIdToAnswerName, m_QuestionIdToQuestionName);
}
protected virtual void SendAnalytic(IAnalytic analytic)
{
EditorAnalytics.SendAnalytic(analytic);
}
public MultiplayerCenterAnalytics(string questionnaireVersion, IReadOnlyDictionary<string, string> questionDisplayNames,
IReadOnlyDictionary<string, string> answerDisplayNames)
{
m_QuestionnaireVersion = questionnaireVersion;
m_QuestionIdToQuestionName = questionDisplayNames;
m_AnswerIdToAnswerName = answerDisplayNames;
}
public void SendGettingStartedInteractionEvent(string targetPackageId, string sectionId, InteractionDataType type, string displayName)
{
var analytic = new GetStartedInteractionEventAnalytic(sectionId, type, displayName, targetPackageId);
SendAnalytic(analytic);
}
public void SendInstallationEvent(AnswerData data, Preset preset, Package[] packages)
{
var analytic = new InstallationEventAnalytic(new InstallData()
{
Preset = (int)preset,
PresetName = PresetName(preset),
QuestionnaireVersion = m_QuestionnaireVersion,
GamesSpecs = FillGameSpecs(data),
Packages = packages
});
SendAnalytic(analytic);
}
public void SendRecommendationEvent(AnswerData data, Preset preset)
{
var analytic = new RecommendationEventAnalytic(new RecommendationData()
{
Preset = (int)preset,
PresetName = PresetName(preset),
QuestionnaireVersion = m_QuestionnaireVersion,
GameSpecs = FillGameSpecs(data)
});
SendAnalytic(analytic);
}
[AnalyticInfo(eventName: k_InstallationEventName, vendorKey: k_VendorKey)]
private class InstallationEventAnalytic : IAnalytic
{
InstallData m_Data;
public InstallationEventAnalytic(InstallData data)
{
m_Data = data;
}
/// <inheritdoc />
public bool TryGatherData(out IAnalytic.IData data, out Exception error)
{
data = m_Data;
error = null;
return true;
}
}
[AnalyticInfo(eventName: k_RecommendationEventName, vendorKey: k_VendorKey)]
private class RecommendationEventAnalytic : IAnalytic
{
RecommendationData m_Data;
public RecommendationEventAnalytic(RecommendationData data)
{
m_Data = data;
}
public bool TryGatherData(out IAnalytic.IData data, out Exception error)
{
data = m_Data;
error = null;
return true;
}
}
[AnalyticInfo(eventName: k_GetStartedInteractionEventName, vendorKey: k_VendorKey)]
private class GetStartedInteractionEventAnalytic : IAnalytic
{
InteractionData m_Data;
public GetStartedInteractionEventAnalytic(string sectionId, InteractionDataType type, string displayName, string targetPackageId)
{
m_Data = new InteractionData()
{
SectionId = sectionId,
Type = type,
DisplayName = displayName,
TargetPackageId = targetPackageId
};
}
/// <inheritdoc />
public bool TryGatherData(out IAnalytic.IData data, out Exception error)
{
data = m_Data;
error = null;
return true;
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 185e678d90804a8a85ab38276eca86fb
timeCreated: 1714382339

View File

@@ -0,0 +1,21 @@
using System;
using Unity.Multiplayer.Center.Questionnaire;
namespace Unity.Multiplayer.Center.Analytics
{
internal static class MultiplayerCenterAnalyticsFactory
{
public static IMultiplayerCenterAnalytics Create()
{
var questionnaire = QuestionnaireObject.instance;
var questionnaireVersion = questionnaire.Questionnaire.Version;
var questionDisplayNames = AnalyticsUtils.GetQuestionDisplayNames(questionnaire.Questionnaire);
var answerDisplayNames = AnalyticsUtils.GetAnswerDisplayNames(questionnaire.Questionnaire);
// Uncomment this line to use the DebugAnalytics class instead of the MultiplayerCenterAnalytics class
// return new DebugAnalytics(questionnaireVersion, questionDisplayNames, answerDisplayNames);
return new MultiplayerCenterAnalytics(questionnaireVersion, questionDisplayNames, answerDisplayNames);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8bce0b24c28e45d884acfcf49a3b8945
timeCreated: 1714640827

View File

@@ -0,0 +1,32 @@
using System;
using Unity.Multiplayer.Center.Common.Analytics;
using UnityEngine;
namespace Unity.Multiplayer.Center.Analytics
{
/// <summary>
/// The concrete implementation of the IOnboardingSectionAnalyticsProvider interface.
/// It shall be created by the GettingStarted tab with the knowledge of the target package and the section id
/// provided by the attribute of the onboarding section, so that the section implementer does not have to worry
/// about it.
/// </summary>
internal class OnboardingSectionAnalyticsProvider : IOnboardingSectionAnalyticsProvider
{
readonly IMultiplayerCenterAnalytics m_Analytics;
readonly string m_TargetPackageId;
readonly string m_SectionId;
public OnboardingSectionAnalyticsProvider(IMultiplayerCenterAnalytics analytics, string targetPackageId, string sectionId)
{
Debug.Assert(analytics != null);
m_Analytics = analytics;
m_TargetPackageId = targetPackageId;
m_SectionId = sectionId;
}
public void SendInteractionEvent(InteractionDataType type, string displayName)
{
m_Analytics.SendGettingStartedInteractionEvent(m_TargetPackageId, m_SectionId, type, displayName);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e9faa4a7023c43c5805642250c703f48
timeCreated: 1714637861

View File

@@ -0,0 +1,5 @@
using System.Runtime.CompilerServices;
[assembly:InternalsVisibleTo("Unity.Multiplayer.Center.Editor.Tests")]
[assembly:InternalsVisibleTo("Unity.Multiplayer.Center.Integrations")]
[assembly:InternalsVisibleTo("Unity.Multiplayer.Center.GettingStartedTab.Tests")]

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 51566b3736df4282bbe76645014b0cc5
timeCreated: 1700487414

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 22d3dbf8d488d49d2b1130d698010dee
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,325 @@
using System;
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.PackageManager;
using UnityEditor.PackageManager.Requests;
using UnityEngine;
namespace Unity.Multiplayer.Center
{
internal static class PackageManagement
{
static PackageInstaller s_Installer;
/// <summary>
/// Opens the package manager window with selected package name and hides error
/// </summary>
public static void OpenPackageManager(string packageName)
{
try
{
UnityEditor.PackageManager.UI.Window.Open(packageName);
}
catch (Exception)
{
// Hide the error in the PackageManager API until the team fixes it
// Debug.Log("Error opening Package Manager: " + e.Message);
}
}
/// <summary>
/// Checks if the package is a direct dependency of the project
/// </summary>
/// <param name="packageId">The package name/id e.g. com.unity.netcode</param>
/// <returns>True if the package is a direct dependency</returns>
public static bool IsDirectDependency(string packageId)
{
var package = GetInstalledPackage(packageId);
return package != null && package.isDirectDependency;
}
/// <summary>
/// Checks if a package is installed.
/// </summary>
/// <param name="packageId">The package name, e.g. com.unity.netcode</param>
/// <returns>True if the package is installed, false otherwise</returns>
public static bool IsInstalled(string packageId) => GetInstalledPackage(packageId) != null;
/// <summary>
/// Checks if a package is embedded, linked locally, installed via Git or local Tarball.
/// </summary>
/// <param name="packageId">The package name, e.g. com.unity.netcode</param>
/// <returns>True if the package is linked locally, false otherwise</returns>
public static bool IsLinkedLocallyOrEmbeddedOrViaGit(string packageId) =>
GetInstalledPackage(packageId) is { source: PackageSource.Embedded or PackageSource.Local or PackageSource.Git or PackageSource.LocalTarball };
/// <summary>
/// Finds the installed package with the given packageId or returns null.
/// </summary>
/// <param name="packageId">The package name/id e.g. com.unity.netcode</param>
/// <returns>The package info</returns>
public static UnityEditor.PackageManager.PackageInfo GetInstalledPackage(string packageId)
{
return UnityEditor.PackageManager.PackageInfo.FindForPackageName(packageId);
}
/// <summary>
/// Filters out the packages that are already embedded, linked locally, installed via Git or local Tarball and returns this new list.
/// </summary>
/// <param name="installCandidates">A list of package IDs that are candidates for installation.</param>
/// <returns>A new filtered list of packages.</returns>
public static IEnumerable<string> RemoveLocallyLinkedOrEmbeddedOrViaGitPackagesFromList(IEnumerable<string> installCandidates)
{
var filteredList = new List<string>();
foreach (var packageId in installCandidates)
{
if (!IsLinkedLocallyOrEmbeddedOrViaGit(packageId))
{
filteredList.Add(packageId);
}
else
{
Debug.Log($"Removing {packageId} from install candidates.\n" +
"This package is already embedded, linked locally, installed via Git, or from a local tarball. " +
"Please check the Package Manager for more information or to upgrade manually.");
}
}
return filteredList;
}
/// <summary>
/// Returns true if any of the given packageIds is installed.
/// </summary>
/// <param name="packageIds">List of package is e.g com.unity.netcode</param>
/// <returns>True if any package is installed, false otherwise</returns>
public static bool IsAnyPackageInstalled(params string[] packageIds)
{
var installedPackages = UnityEditor.PackageManager.PackageInfo.GetAllRegisteredPackages();
var hashset = new HashSet<string>();
foreach (var package in installedPackages)
{
hashset.Add(package.name);
}
foreach (var packageId in packageIds)
{
if (hashset.Contains(packageId))
{
return true;
}
}
return false;
}
/// <summary>
/// Installs a single package and invokes the callback when the package is installed/when the install failed.
/// </summary>
/// <param name="packageId">The package name/id e.g. com.unity.netcode</param>
/// <param name="onInstalled">The callback</param>
public static void InstallPackage(string packageId, Action<bool> onInstalled = null)
{
s_Installer = new PackageInstaller(packageId);
s_Installer.OnInstalled += onInstalled;
s_Installer.OnInstalled += _ => s_Installer = null;
}
/// <summary>
/// Register to an existing installation callback. This has no effect if no installation is ongoing (check
/// <see cref="IsInstallationFinished"/> to see if that is the case).
/// </summary>
/// <param name="onInstalled">The callback</param>
public static void RegisterToExistingInstaller(Action<bool> onInstalled)
{
if (s_Installer != null)
{
s_Installer.OnInstalled += onInstalled;
}
}
/// <summary>
/// Installs several packages and invokes the callback when all packages are installed/when the installation failed.
/// </summary>
/// <param name="packageIds">The package names/ids e.g. com.unity.netcode</param>
/// <param name="onAllInstalled">The callback</param>
/// <param name="packageIdsToRemove">Optional package name/ids to remove</param>
public static void InstallPackages(IEnumerable<string> packageIds, Action<bool> onAllInstalled = null, IEnumerable<string> packageIdsToRemove = null)
{
s_Installer = new PackageInstaller(RemoveLocallyLinkedOrEmbeddedOrViaGitPackagesFromList(packageIds), packageIdsToRemove);
s_Installer.OnInstalled += onAllInstalled;
s_Installer.OnInstalled += _ => s_Installer = null;
}
/// <summary>
/// Create a dictionary with package names as keys and versions as values
/// </summary>
/// <returns>The mapping (package id, installed version) </returns>
internal static Dictionary<string, string> InstalledPackageDictionary()
{
var installedPackages = UnityEditor.PackageManager.PackageInfo.GetAllRegisteredPackages();
var installedPackageDictionary = new Dictionary<string, string>();
foreach (var package in installedPackages)
{
var splitPackageId = package.packageId.Split('@');
if (splitPackageId.Length == 2)
{
installedPackageDictionary[splitPackageId[0]] = splitPackageId[1];
}
}
return installedPackageDictionary;
}
internal class VersionChecker
{
SearchRequest m_Request;
public VersionChecker(string packageID)
{
m_Request = Client.Search(packageID, false);
EditorApplication.update += Progress;
}
public event Action<UnityEditor.PackageManager.PackageInfo> OnVersionFound;
void Progress()
{
if (!m_Request.IsCompleted) return;
EditorApplication.update -= Progress;
var foundPackage = m_Request.Result;
foreach (var packageInfo in foundPackage)
{
OnVersionFound?.Invoke(packageInfo);
}
}
}
class PackageInstaller
{
Request m_Request;
string[] m_PackagesToAddIds;
public event Action<bool> OnInstalled;
public PackageInstaller(string packageId)
{
// Add a package to the project
m_Request = Client.Add(packageId);
m_PackagesToAddIds = new[] {packageId};
EditorApplication.update += Progress;
}
public PackageInstaller(IEnumerable<string> packageIds, IEnumerable<string> packageIdsToRemove = null)
{
var packageIdsList = new List<string>();
foreach (var id in packageIds)
{
packageIdsList.Add(id);
}
var packageIdsArray = packageIdsList.ToArray();
string[] packageIdsToRemoveArray = null;
if (packageIdsToRemove != null)
{
var packageIdsToRemoveList = new List<string>();
foreach (var id in packageIdsToRemove)
{
packageIdsToRemoveList.Add(id);
}
packageIdsToRemoveArray = packageIdsToRemoveList.ToArray();
}
// Add a package to the project
m_Request = Client.AddAndRemove(packageIdsArray, packageIdsToRemoveArray);
m_PackagesToAddIds = packageIdsArray;
EditorApplication.update += Progress;
}
public bool IsCompleted()
{
return m_Request == null || m_Request.IsCompleted;
}
void Progress()
{
if (!m_Request.IsCompleted) return;
EditorApplication.update -= Progress;
if (m_Request.Status == StatusCode.Success)
{
Debug.Log("Installed: " + GetInstalledPackageId());
}
else if (m_Request.Status >= StatusCode.Failure)
{
// if the request has more than one package, it will only prompt error message for one
// We should prompt all the failed packages
Debug.Log("Package installation request with selected packages: " + String.Join(", ", m_PackagesToAddIds) +
" failed. \n Reason: "+ m_Request.Error.message);
}
OnInstalled?.Invoke(m_Request.Status == StatusCode.Success);
}
string GetInstalledPackageId()
{
switch (m_Request)
{
case AddRequest addRequest:
return addRequest.Result.packageId;
case AddAndRemoveRequest addAndRemoveRequest:
var packageIds = new List<string>();
foreach (var packageInfo in addAndRemoveRequest.Result)
{
packageIds.Add(packageInfo.packageId);
}
return string.Join(", ", packageIds);
default:
throw new InvalidOperationException("Unknown request type");
}
}
}
/// <summary>
/// Detects if any multiplayer package is installed by checking for services and Netcode installed packages.
/// </summary>
/// <returns>True if any package was detected, False otherwise</returns>
public static bool IsAnyMultiplayerPackageInstalled()
{
var packagesToCheck = new []
{
"com.unity.netcode",
"com.unity.netcode.gameobjects",
"com.unity.services.multiplayer",
"com.unity.transport",
"com.unity.dedicated-server",
"com.unity.services.cloudcode",
"com.unity.multiplayer.playmode",
"com.unity.services.vivox"
// Note about "com.unity.services.core": it used to be installed only with multiplayer packages, but it is also a dependency of the analytics, which is now always installed.
};
foreach (var package in packagesToCheck)
{
if (IsInstalled(package))
{
return true;
}
}
return false;
}
/// <summary>
/// Checks if the installation process has finished.
/// </summary>
/// <returns>True if there is no current installer instance or installation is finished on the installer</returns>
public static bool IsInstallationFinished()
{
return s_Installer == null || s_Installer.IsCompleted();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 676a3852811f4199813ce7f6dbac6880
timeCreated: 1694784710

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7a7dcde6448847648629a13d746ce966
timeCreated: 1695036776

View File

@@ -0,0 +1,225 @@
using System;
using Unity.Multiplayer.Center.Analytics;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace Unity.Multiplayer.Center.Window
{
internal class MultiplayerCenterWindow : EditorWindow, ISerializationCallbackReceiver
{
const string k_PathInPackage = "Packages/com.unity.multiplayer.center/Editor/MultiplayerCenterWindow";
const string k_SpinnerClassName = "processing";
const string k_SessionStateDomainReloadKey = "MultiplayerCenter.InDomainReload";
VisualElement m_SpinningIcon;
/// <summary>
/// Nest the main container in a VisualElement to allow for easy enabling/disabling of the entire window but
/// without the spinning icon.
/// </summary>
VisualElement m_MainContainer;
Vector2 m_WindowSize = new(350, 300);
public int CurrentTab => m_TabGroup.CurrentTab;
// Testing purposes only. We don't want to set CurrentTab from window
internal int CurrentTabTest
{
get => m_TabGroup.CurrentTab;
set => m_TabGroup.SetSelected(value);
}
[SerializeField]
bool m_RequestGettingStartedTabAfterDomainReload = false;
[SerializeField]
TabGroup m_TabGroup;
/// <summary>
/// This is the reference Multiplayer Center analytics implementation. This class owns it.
/// </summary>
IMultiplayerCenterAnalytics m_MultiplayerCenterAnalytics;
IMultiplayerCenterAnalytics MultiplayerCenterAnalytics => m_MultiplayerCenterAnalytics ??= MultiplayerCenterAnalyticsFactory.Create();
[MenuItem("Window/Multiplayer/Multiplayer Center")]
public static void OpenWindow()
{
var showUtility = false; // TODO: figure out if it would be a good idea to have a utility window (always on top, cannot be tabbed)
GetWindow<MultiplayerCenterWindow>(showUtility, "Multiplayer Center", true);
}
void OnEnable()
{
// Adjust window size based on dpi scaling
var dpiScale = EditorGUIUtility.pixelsPerPoint;
minSize = new Vector2(m_WindowSize.x * dpiScale, m_WindowSize.y * dpiScale);
AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeDomainReload;
AssemblyReloadEvents.afterAssemblyReload -= OnAfterDomainReload;
AssemblyReloadEvents.beforeAssemblyReload += OnBeforeDomainReload;
AssemblyReloadEvents.afterAssemblyReload += OnAfterDomainReload;
}
void OnDisable()
{
AssemblyReloadEvents.beforeAssemblyReload -= OnBeforeDomainReload;
AssemblyReloadEvents.afterAssemblyReload -= OnAfterDomainReload;
}
/// <summary>
/// Changes Tab from Recommendation to the Quickstart tab.
/// </summary>
public void RequestShowGettingStartedTabAfterDomainReload()
{
m_RequestGettingStartedTabAfterDomainReload = true;
// If no domain reload is necessary, this will be called.
// If domain reload is necessary, the delay call will be forgotten, but CreateGUI will be called like after any domain reload
// An extra delay is added to make sure that the visibility conditions of the Quickstart tab have been
// fully evaluated. This solves MTT-8939.
EditorApplication.delayCall += () =>
{
rootVisualElement.schedule.Execute(CallCreateGuiWithQuickstartRequest).ExecuteLater(300);
};
}
internal void DisableUiForInstallation()
{
SetSpinnerIconRotating();
m_MainContainer.SetEnabled(false);
}
internal void ReenableUiAfterInstallation()
{
RemoveSpinnerIconRotating();
m_MainContainer.SetEnabled(true);
}
void Update()
{
// Restore the GUI if it was cleared in OnBeforeSerialize.
if (m_TabGroup == null || m_TabGroup.ViewCount < 1)
{
CreateGUI();
}
}
void CreateGUI()
{
rootVisualElement.name = "root";
m_MainContainer ??= new VisualElement();
m_MainContainer.name = "recommendation-tab-container";
m_MainContainer.Clear();
rootVisualElement.Add(m_MainContainer);
m_SpinningIcon = new VisualElement();
var theme = EditorGUIUtility.isProSkin ? "dark" : "light";
rootVisualElement.styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>($"{k_PathInPackage}/UI/{theme}.uss"));
rootVisualElement.styleSheets.Add(AssetDatabase.LoadAssetAtPath<StyleSheet>($"{k_PathInPackage}/UI/MultiplayerCenterWindow.uss"));
if (m_TabGroup == null || m_TabGroup.ViewCount < 1 || !m_TabGroup.TabsAreValid())
m_TabGroup = new TabGroup(MultiplayerCenterAnalytics, new ITabView[] {new RecommendationTabView(), new GettingStartedTabView()});
else // since we are not serializing the analytics provider, we need to set it again
m_TabGroup.MultiplayerCenterAnalytics = MultiplayerCenterAnalytics;
m_TabGroup.CreateTabs();
m_MainContainer.Add(m_TabGroup.Root);
var installationInProgress = !PackageManagement.IsInstallationFinished();
SetWindowContentEnabled(installationInProgress, m_RequestGettingStartedTabAfterDomainReload);
ShowAppropriateTab(installationInProgress);
}
void ShowAppropriateTab(bool installationInProgress)
{
if (installationInProgress)
{
PackageManagement.RegisterToExistingInstaller(b => RequestShowGettingStartedTabAfterDomainReload());
m_TabGroup.SetSelected(0, force: true);
return;
}
if (m_RequestGettingStartedTabAfterDomainReload)
{
m_RequestGettingStartedTabAfterDomainReload = false;
m_TabGroup.SetSelected(1, force: true);
}
else
{
m_TabGroup.SetSelected(m_TabGroup.CurrentTab, force: true);
}
}
void SetWindowContentEnabled(bool installationInProgress, bool quickstartRequested)
{
m_MainContainer.SetEnabled(!installationInProgress || quickstartRequested);
// if we are current already processing an installation, show the spinning icon
if (installationInProgress)
{
// Wait a bit because the animation does not trigger when we call this in CreateGUI
EditorApplication.delayCall += SetSpinnerIconRotating;
}
rootVisualElement.Add(m_SpinningIcon);
}
void CallCreateGuiWithQuickstartRequest()
{
// Interestingly, setting this before registering the delay call sometimes results in the value
// being false when CreateGUI starts, so we set it again here.
m_RequestGettingStartedTabAfterDomainReload = true;
CreateGUI();
}
void SetSpinnerIconRotating()
{
m_SpinningIcon.AddToClassList(k_SpinnerClassName);
}
void RemoveSpinnerIconRotating()
{
m_SpinningIcon?.RemoveFromClassList(k_SpinnerClassName);
}
void ClearTabs()
{
m_TabGroup?.Clear();
m_TabGroup = null;
}
// This will not get called when the Editor is closed.
void OnDestroy()
{
ClearTabs();
}
static void OnBeforeDomainReload()
{
SessionState.SetBool(k_SessionStateDomainReloadKey, true);
}
static void OnAfterDomainReload()
{
SessionState.SetBool(k_SessionStateDomainReloadKey, false);
}
public void OnBeforeSerialize()
{
// ClearTabs if the Window gets serialized, but we are not in DomainReload
// This happens when the Editor closes or the WindowLayout is saved by the user.
// This ensures that the State of the Tabs is not serialized into the WindowLayout of the User.
if (SessionState.GetBool(k_SessionStateDomainReloadKey, false) == false)
{
ClearTabs();
}
}
public void OnAfterDeserialize()
{
// Empty on purpose.
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 7e006510f5f4425ca971c7de0bfbb77a
timeCreated: 1695036798

View File

@@ -0,0 +1,197 @@
using System;
using Unity.Multiplayer.Center.Analytics;
using Unity.Multiplayer.Center.Common;
using Unity.Multiplayer.Center.Questionnaire;
using Unity.Multiplayer.Center.Recommendations;
using Unity.Multiplayer.Center.Window.UI;
using Unity.Multiplayer.Center.Window.UI.RecommendationView;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace Unity.Multiplayer.Center.Window
{
internal class RecommendationTabView : ITabView
{
QuestionnaireView m_QuestionnaireView;
RecommendationView m_RecommendationView;
RecommendationViewBottomBar m_BottomBarView;
bool m_IsVisible;
bool m_ShouldRefresh = true;
[SerializeField]
PreReleaseHandling m_PreReleaseHandling = new();
[field: SerializeField] // Marked as redundant by Rider, but it is not.
public string Name { get; private set; }
public VisualElement RootVisualElement { get; set; }
public IMultiplayerCenterAnalytics MultiplayerCenterAnalytics { get; set; }
// Access to QuestionnaireView for testing purposes
internal QuestionnaireView QuestionnaireView => m_QuestionnaireView;
public RecommendationTabView(string name = "Recommendation")
{
Name = name;
m_PreReleaseHandling.OnAllChecksFinished += PatchData;
m_PreReleaseHandling.CheckForUpdates();
Undo.undoRedoPerformed += OnUndoRedoPerformed;
}
void OnUndoRedoPerformed()
{
UserChoicesObject.instance.Save();
m_QuestionnaireView?.Refresh();
UpdateRecommendation(keepSelection:true);
}
public void Clear()
{
Undo.undoRedoPerformed -= OnUndoRedoPerformed;
m_RecommendationView?.Clear();
m_QuestionnaireView?.Clear();
RootVisualElement?.Clear();
m_QuestionnaireView = null;
m_RecommendationView = null;
}
public void SetVisible(bool visible)
{
RootVisualElement.style.display = visible ? DisplayStyle.Flex : DisplayStyle.None;
m_IsVisible = visible;
}
public void Refresh()
{
Debug.Assert(MultiplayerCenterAnalytics != null, "MultiplayerCenterAnalytics != null");
RefreshPreReleaseHandling();
if (!m_ShouldRefresh && RootVisualElement.childCount > 0) return;
CreateStandardView();
m_ShouldRefresh = false;
}
void RefreshPreReleaseHandling()
{
if (!m_PreReleaseHandling.IsReady)
{
m_PreReleaseHandling.OnAllChecksFinished += PatchData;
m_PreReleaseHandling.CheckForUpdates();
}
else
{
m_PreReleaseHandling.PatchRecommenderSystemData();
}
}
void CreateStandardView()
{
Clear();
Undo.undoRedoPerformed -= OnUndoRedoPerformed;
Undo.undoRedoPerformed += OnUndoRedoPerformed;
MigrateUserChoices();
// We need this because Bottom bar is a part of the Recommendations Tab and it should always stay
// at the bottom of the view. So we need to make sure that the root tab element is always 100% height.
RootVisualElement.style.height = Length.Percent(100);
var horizontalContainer = new TwoPaneSplitView(0, 250, TwoPaneSplitViewOrientation.Horizontal);
horizontalContainer.AddToClassList(StyleClasses.MainSplitView);
horizontalContainer.name = "recommendation-tab-split-view";
// This is used to make sure the left side does not grow to 100% as this is what would happen by default.
// It feels not 100% correct. But it seems to be the only way to match the height of the 2 sides with how
// our views are build currently.
horizontalContainer.contentContainer.style.position = Position.Relative;
m_QuestionnaireView = new QuestionnaireView(QuestionnaireObject.instance.Questionnaire);
m_QuestionnaireView.OnQuestionnaireDataChanged += HandleQuestionnaireDataChanged;
m_QuestionnaireView.OnPresetSelected += OnPresetSelected;
m_QuestionnaireView.Root.AddToClassList(StyleClasses.MainSplitViewLeft);
horizontalContainer.Add(m_QuestionnaireView.Root);
m_RecommendationView = new RecommendationView();
m_RecommendationView.Root.AddToClassList(StyleClasses.MainSplitViewRight);
horizontalContainer.Add(m_RecommendationView.Root);
RootVisualElement.Add(horizontalContainer);
m_BottomBarView = new RecommendationViewBottomBar(MultiplayerCenterAnalytics);
m_RecommendationView.OnPackageSelectionChanged +=
() => m_BottomBarView.UpdatePackagesToInstall(m_RecommendationView.CurrentRecommendation, m_RecommendationView.AllPackages);
RootVisualElement.Add(m_BottomBarView);
UpdateRecommendation(keepSelection: true);
m_BottomBarView.SetInfoTextForCheckingPackages(!m_PreReleaseHandling.IsReady);
}
void HandleQuestionnaireDataChanged()
{
UpdateRecommendation(keepSelection: false);
}
static void MigrateUserChoices()
{
var questionnaire = QuestionnaireObject.instance.Questionnaire;
var userChoices = UserChoicesObject.instance;
// make sure the version of the questionnaire is the same as the one in the user choices.
if (questionnaire.Version != userChoices.QuestionnaireVersion && userChoices.UserAnswers.Answers.Count > 0)
{
Logic.MigrateUserChoices(questionnaire, userChoices);
}
}
void UpdateRecommendation(bool keepSelection)
{
var questionnaire = QuestionnaireObject.instance.Questionnaire;
var userChoices = UserChoicesObject.instance;
var errors = Logic.ValidateAnswers(questionnaire, userChoices.UserAnswers);
foreach (var error in errors)
{
Debug.LogError(error);
}
var recommendation = userChoices.Preset == Preset.None? null
: RecommenderSystem.GetRecommendation(questionnaire, userChoices.UserAnswers);
m_PreReleaseHandling.PatchPackages(recommendation);
if (keepSelection)
{
RecommendationUtils.ApplyPreviousSelection(recommendation, userChoices.SelectedSolutions);
}
else if (recommendation != null) // we only send the event if there is a recommendation and it is a new one
{
MultiplayerCenterAnalytics.SendRecommendationEvent(userChoices.UserAnswers, userChoices.Preset);
}
m_RecommendationView.UpdateRecommendation(recommendation, m_PreReleaseHandling);
m_BottomBarView.UpdatePackagesToInstall(recommendation, m_RecommendationView.AllPackages);
}
void PatchData()
{
m_PreReleaseHandling.PatchRecommenderSystemData();
m_PreReleaseHandling.OnAllChecksFinished -= PatchData;
m_ShouldRefresh = true;
if(m_IsVisible)
Refresh();
}
void OnPresetSelected(Preset preset)
{
var (resultAnswerData, recommendation) = Logic.ApplyPresetToAnswerData(
UserChoicesObject.instance.UserAnswers, preset, QuestionnaireObject.instance.Questionnaire);
UserChoicesObject.instance.UserAnswers = resultAnswerData;
UserChoicesObject.instance.Save();
if (recommendation != null)
MultiplayerCenterAnalytics.SendRecommendationEvent(resultAnswerData, preset);
m_QuestionnaireView.Refresh();
m_RecommendationView.UpdateRecommendation(recommendation, m_PreReleaseHandling);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 052c764978664486a9c04518899ddf57
timeCreated: 1700489744

View File

@@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using Unity.Multiplayer.Center.Analytics;
using Unity.Multiplayer.Center.Questionnaire;
using Unity.Multiplayer.Center.Recommendations;
using Unity.Multiplayer.Center.Window.UI;
using UnityEditor;
using UnityEngine.UIElements;
namespace Unity.Multiplayer.Center.Window
{
class RecommendationViewBottomBar : VisualElement
{
readonly Label m_PackageCount;
readonly Button m_InstallPackageButton;
readonly Label m_InfoLabel;
IMultiplayerCenterAnalytics m_Analytics;
MultiplayerCenterWindow m_Window = EditorWindow.GetWindow<MultiplayerCenterWindow>();
List<string> m_PackagesToInstallIds = new ();
List<string> m_PackagesToInstallNames = new ();
RecommendationViewData m_RecommendationViewData;
SolutionsToRecommendedPackageViewData m_SolutionToPackageData;
public RecommendationViewBottomBar(IMultiplayerCenterAnalytics analytics)
{
m_Analytics = analytics;
name = "bottom-bar";
m_PackageCount = new Label {name = "package-count"};
m_InfoLabel = new Label();
// Setup Install Button
m_InstallPackageButton = new Button(OnInstallButtonClicked) {text = "Install Packages"};
m_InstallPackageButton.AddToClassList(StyleClasses.NextStepButton);
// Put the button in a container
var installPackageContainer = new VisualElement() {name = "install-package-container"};
installPackageContainer.Add(m_InstallPackageButton);
Add(m_PackageCount);
Add(m_InfoLabel);
Add(installPackageContainer);
}
void OnInstallButtonClicked()
{
if (!PackageManagement.IsAnyMultiplayerPackageInstalled() || WarnDialogForPackageInstallation())
{
SendInstallationAnalyticsEvent();
InstallSelectedPackagesAndExtension();
}
}
void SendInstallationAnalyticsEvent()
{
var answerObject = UserChoicesObject.instance;
m_Analytics.SendInstallationEvent(answerObject.UserAnswers, answerObject.Preset,
AnalyticsUtils.GetPackagesWithAnalyticsFormat(m_RecommendationViewData, m_SolutionToPackageData));
}
bool WarnDialogForPackageInstallation()
{
var warningMessage =
"Ensure compatibility with your current multiplayer packages before installing or upgrading the following:\n" +
string.Join("\n", m_PackagesToInstallNames);
return EditorUtility.DisplayDialog("Install Packages", warningMessage, "OK", "Cancel");
}
void InstallSelectedPackagesAndExtension()
{
SetInfoTextForInstallation(isInstalling:true);
m_Window.DisableUiForInstallation();
PackageManagement.InstallPackages(m_PackagesToInstallIds, onAllInstalled: OnInstallationFinished);
}
void OnInstallationFinished(bool success)
{
SetInfoTextForInstallation(isInstalling:false);
m_Window.RequestShowGettingStartedTabAfterDomainReload();
m_Window.ReenableUiAfterInstallation();
}
public void UpdatePackagesToInstall(RecommendationViewData data, SolutionsToRecommendedPackageViewData packageViewData)
{
m_RecommendationViewData = data;
m_SolutionToPackageData = packageViewData;
var packages = RecommendationUtils.PackagesToInstall(data, packageViewData);
RecommendationUtils.GetPackagesWithAdditionalPackages(packages, out m_PackagesToInstallIds, out m_PackagesToInstallNames, out var toolTip);
m_PackageCount.tooltip = toolTip;
// Note: quickstart is counted in the list of packages to install, but not the names
m_PackageCount.text = $"Packages to install: {m_PackagesToInstallNames.Count}";
// if the list is empty, disable the button
m_InstallPackageButton.SetEnabled(m_PackagesToInstallNames.Count > 0);
}
internal void SetInfoTextForInstallation(bool isInstalling)
{
SetInfoLabelTextAndVisibility("Downloading packages, please wait ...", isInstalling);
}
internal void SetInfoTextForCheckingPackages(bool isChecking)
{
SetInfoLabelTextAndVisibility("Querying packages information ...", isChecking);
// Handle the case of reopening the window during the installation.
// When reopening the window, the packages are being checked. Once that check is done, we still want to
// display the installation package text if there is an ongoing installation.
if(!isChecking && !PackageManagement.IsInstallationFinished())
SetInfoTextForInstallation(isInstalling:true);
}
void SetInfoLabelTextAndVisibility(string text, bool isVisible)
{
m_InfoLabel.text = text;
m_InfoLabel.visible = isVisible;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: d1084186a4004823abee9b87c8ddf198
timeCreated: 1710930997

View File

@@ -0,0 +1,197 @@
using System;
using Unity.Multiplayer.Center.Analytics;
using UnityEditor;
using UnityEngine;
using UnityEngine.UIElements;
namespace Unity.Multiplayer.Center.Window
{
// Note: there is a TabView API in UI Toolkit, but only starting from 2023.2
internal interface ITabView
{
/// <summary>
/// The name as displayed in the tab button
/// Should be serialized.
/// </summary>
string Name { get; }
/// <summary>
/// The root visual element of the tab view.
/// The setter will only be used if the root visual element is null when the tab is created.
/// </summary>
VisualElement RootVisualElement { get; set; }
/// <summary>
/// Sets the tab view visible or not.
/// </summary>
/// <param name="visible">If true, visible.</param>
void SetVisible(bool visible);
/// <summary>
/// If true the Tab can be selected by the user.
/// </summary>
bool IsEnabled => true;
/// <summary>
/// Tooltip which will be shown on the Tab Button.
/// </summary>
string ToolTip => "";
/// <summary>
/// Refreshes the UI Elements according to latest data.
/// If the UI is not created yet, it does it.
/// </summary>
void Refresh();
/// <summary>
/// Unregister all events and clear UI Elements
/// </summary>
void Clear();
/// <summary>
/// The Multiplayer Center Analytics provider.
/// </summary>
IMultiplayerCenterAnalytics MultiplayerCenterAnalytics { get; set; }
}
[Serializable]
internal class TabGroup
{
const string k_TabViewName = "tab-view";
const string k_TabZoneName = "tab-zone";
const string k_TabButtonUssClass = "tab-button";
// The container for all the tabs
const string k_TabsContainerUssClass ="tabs-container";
// Gets applied to the root of each tab
const string k_TabContentUssClass = "tab-content";
[field: SerializeField]
public int CurrentTab { get; private set; } = -1;
public int ViewCount => m_TabViews?.Length ?? 0;
VisualElement[] m_TabButtons;
[SerializeReference]
ITabView[] m_TabViews;
public VisualElement Root { get; private set; }
VisualElement m_MainContainer;
IMultiplayerCenterAnalytics m_MultiplayerCenterAnalytics;
internal IMultiplayerCenterAnalytics MultiplayerCenterAnalytics
{
get => m_MultiplayerCenterAnalytics;
set
{
m_MultiplayerCenterAnalytics = value;
foreach (var tabView in m_TabViews)
{
if(tabView != null)
tabView.MultiplayerCenterAnalytics = value;
}
}
}
public TabGroup(IMultiplayerCenterAnalytics analytics, ITabView[] tabViews, int defaultIndex = 0)
{
m_TabViews = tabViews;
CurrentTab = defaultIndex;
MultiplayerCenterAnalytics = analytics;
}
public void SetSelected(int index, bool force = false)
{
// Select the first tab, if the requested tab is not enabled.
// This assumes the first tab is always enabled.
if (!m_TabViews[index].IsEnabled)
index = 0;
if (index == CurrentTab && !force)
return;
if (CurrentTab >= 0 && CurrentTab < m_TabViews.Length)
{
m_TabButtons[CurrentTab].RemoveFromClassList("selected");
m_TabViews[CurrentTab].SetVisible(false);
}
EditorPrefs.SetInt(PlayerSettings.productName + "_MultiplayerCenter_TabIndex", index);
CurrentTab = index;
m_TabViews[CurrentTab].Refresh();
m_TabButtons[CurrentTab].AddToClassList("selected");
m_TabViews[CurrentTab].SetVisible(true);
}
/// <summary>
/// Instantiates the visual elements for all the tabs.
/// Use this to create the tabs for the first time the UI is shown or after a domain reload.
/// </summary>
public void CreateTabs()
{
Root ??= new VisualElement();
m_MainContainer ??= new VisualElement();
if (Root.Q(k_TabZoneName) != null)
Root.Q(k_TabZoneName).RemoveFromHierarchy();
var tabZone = new VisualElement() {name = k_TabZoneName};
Root.Add(tabZone);
Root.name = k_TabViewName;
m_TabButtons = new VisualElement[m_TabViews.Length];
for (var i = 0; i < m_TabViews.Length; i++)
{
var tabView = m_TabViews[i];
var index = i; // copy for closure
var tabButton = new Button(() => SetSelected(index));
tabButton.enabledSelf = tabView.IsEnabled;
tabButton.tooltip = tabView.ToolTip;
tabButton.AddToClassList(k_TabButtonUssClass);
tabButton.text = tabView.Name;
tabZone.Add(tabButton);
m_TabButtons[i] = tabButton;
tabView.RootVisualElement ??= new VisualElement();
tabView.RootVisualElement.AddToClassList(k_TabContentUssClass);
tabView.RootVisualElement.style.display = DisplayStyle.None;
m_MainContainer.Add(m_TabViews[i].RootVisualElement);
}
m_MainContainer.AddToClassList(k_TabsContainerUssClass);
Root.Add(m_MainContainer);
CurrentTab = EditorPrefs.GetInt(PlayerSettings.productName + "_MultiplayerCenter_TabIndex", 0);
}
static void SetVisible(VisualElement e, bool visible)
{
e.style.display = visible ? DisplayStyle.Flex : DisplayStyle.None;
}
public void Clear()
{
if(m_TabViews == null)
return;
foreach (var tabView in m_TabViews)
{
tabView?.Clear();
}
}
public bool TabsAreValid()
{
if (m_TabViews == null)
return false;
foreach (var tab in m_TabViews)
{
if (tab == null)
return false;
}
return true;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: fdefed6b790f4373b0feb7d25854d8ac
timeCreated: 1700582398

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 9bd34ec3fe8f4aed936c3a0cf2f32e56
timeCreated: 1695037065

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 34a09eb4d6e8d44989194a25525c5147
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@@ -0,0 +1,127 @@
fileFormatVersion: 2
guid: 4f78a544322c742b89e63fb68557b1d2
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 12
mipmaps:
mipMapMode: 0
enableMipMap: 0
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 1
wrapV: 1
wrapW: 0
nPOTScale: 0
lightmap: 0
compressionQuality: 50
spriteMode: 1
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 1
spriteTessellationDetail: -1
textureType: 2
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 3
buildTarget: DefaultTexturePlatform
maxTextureSize: 32
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 3
buildTarget: Server
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
physicsShape: []
bones: []
spriteID: 5e97eb03825dee720800000000000000
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,128 @@
fileFormatVersion: 2
guid: b2ce704e56cc84fb3b347499263c6244
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,128 @@
fileFormatVersion: 2
guid: c7a38e6eccbfc49778cb8b77f594a971
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,128 @@
fileFormatVersion: 2
guid: ad1d29f4654194951a3c8bf507914d05
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,128 @@
fileFormatVersion: 2
guid: a23c0dd570fd44b57a03a8880002fcca
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,128 @@
fileFormatVersion: 2
guid: dce12af736e0a4a1ba35d6424f897dc9
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,128 @@
fileFormatVersion: 2
guid: d7711b0cc806d430b8a95f1e33ec3649
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,128 @@
fileFormatVersion: 2
guid: 3fe892784421e47f5aa40c2784a6cb3e
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,128 @@
fileFormatVersion: 2
guid: 38b78df4a34c94fa6a52c90239606ff1
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,128 @@
fileFormatVersion: 2
guid: d63245ece6d8f476c8c7ca24da9937f6
TextureImporter:
internalIDToNameTable: []
externalObjects: {}
serializedVersion: 13
mipmaps:
mipMapMode: 0
enableMipMap: 1
sRGBTexture: 1
linearTexture: 0
fadeOut: 0
borderMipMap: 0
mipMapsPreserveCoverage: 0
alphaTestReferenceValue: 0.5
mipMapFadeDistanceStart: 1
mipMapFadeDistanceEnd: 3
bumpmap:
convertToNormalMap: 0
externalNormalMap: 0
heightScale: 0.25
normalMapFilter: 0
flipGreenChannel: 0
isReadable: 0
streamingMipmaps: 0
streamingMipmapsPriority: 0
vTOnly: 0
ignoreMipmapLimit: 0
grayScaleToAlpha: 0
generateCubemap: 6
cubemapConvolution: 0
seamlessCubemap: 0
textureFormat: 1
maxTextureSize: 2048
textureSettings:
serializedVersion: 2
filterMode: 1
aniso: 1
mipBias: 0
wrapU: 0
wrapV: 0
wrapW: 0
nPOTScale: 1
lightmap: 0
compressionQuality: 50
spriteMode: 0
spriteExtrude: 1
spriteMeshType: 1
alignment: 0
spritePivot: {x: 0.5, y: 0.5}
spritePixelsToUnits: 100
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
spriteGenerateFallbackPhysicsShape: 1
alphaUsage: 1
alphaIsTransparency: 0
spriteTessellationDetail: -1
textureType: 0
textureShape: 1
singleChannelComponent: 0
flipbookRows: 1
flipbookColumns: 1
maxTextureSizeSet: 0
compressionQualitySet: 0
textureFormatSet: 0
ignorePngGamma: 0
applyGammaDecoding: 0
swizzle: 50462976
cookieLightType: 0
platformSettings:
- serializedVersion: 4
buildTarget: DefaultTexturePlatform
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: Standalone
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
- serializedVersion: 4
buildTarget: iOS
maxTextureSize: 2048
resizeAlgorithm: 0
textureFormat: -1
textureCompression: 1
compressionQuality: 50
crunchedCompression: 0
allowsAlphaSplitting: 0
overridden: 0
ignorePlatformSupport: 0
androidETC2FallbackOverride: 0
forceMaximumCompressionQuality_BC6H_BC7: 0
spriteSheet:
serializedVersion: 2
sprites: []
outline: []
customData:
physicsShape: []
bones: []
spriteID:
internalID: 0
vertices: []
indices:
edges: []
weights: []
secondaryTextures: []
nameFileIdTable: {}
mipmapLimitGroupName:
pSDRemoveMatte: 0
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More