Repository: egametang/ET Branch: release9.0 Commit: 9bcbf5d184f5 Files: 304 Total size: 856.8 KB Directory structure: gitextract_fkscp4eu/ ├── .gitattributes ├── .gitignore ├── Assets/ │ ├── .gitignore │ ├── DefaultVolumeProfile.asset │ ├── DefaultVolumeProfile.asset.meta │ ├── Resources/ │ │ ├── BuildinFileManifest.asset │ │ └── BuildinFileManifest.asset.meta │ ├── Resources.meta │ ├── Settings/ │ │ ├── Build Profiles/ │ │ │ ├── Mac.asset │ │ │ └── Mac.asset.meta │ │ └── Build Profiles.meta │ ├── Settings.meta │ ├── link.xml │ └── link.xml.meta ├── Book/ │ ├── 1.1运行指南.md │ ├── 1.2Why use .net core.md │ ├── 1.2为什么使用.net core.md │ ├── 2.1CSharp Coroutine.md │ ├── 2.1CSharp的协程.md │ ├── 2.2Better Coroutine.md │ ├── 2.2更好的协程.md │ ├── 2.3Single-threaded asynchronous.md │ ├── 2.3单线程异步.md │ ├── 3.2The powerful MongoBson library.md │ ├── 3.2强大的MongoBson库.md │ ├── 3.3Everything is Entity.md │ ├── 3.3一切皆实体.md │ ├── 3.4EventSystem.md │ ├── 3.4事件机制EventSystem.md │ ├── 4.1Component-based design.md │ ├── 4.1组件式设计.md │ ├── 5.4Actor Model.md │ ├── 5.4Actor模型.md │ ├── 5.5Actor Location-EN.md │ ├── 5.5Actor Location-ZH.md │ ├── 5.6Numerical component design.md │ ├── 5.6数值组件设计.md │ ├── 6.1AI Framwork.md │ ├── 6.1AI框架.md │ ├── 6.2AI框架-行为机.md │ ├── 7.1代码规范.md │ ├── 8.1ET Package制作指南.md │ ├── 8.2ET Package目录.md │ └── 8.3ET9项目怎么进行包更新.md ├── ChangeLog.md ├── Directory.Build.props ├── ET.sln.DotSettings ├── LICENSE ├── Packages/ │ ├── .gitignore │ ├── com.etetet.init/ │ │ ├── .gitignore │ │ ├── Editor/ │ │ │ ├── ET.Init.Editor.asmdef │ │ │ ├── ET.Init.Editor.asmdef.meta │ │ │ ├── GitDependencyResolver/ │ │ │ │ ├── DependencyResolver.cs │ │ │ │ └── DependencyResolver.cs.meta │ │ │ ├── GitDependencyResolver.meta │ │ │ ├── PackageGit.cs │ │ │ ├── PackageGit.cs.meta │ │ │ ├── ProcessHelper.cs │ │ │ └── ProcessHelper.cs.meta │ │ ├── Editor.meta │ │ ├── MoveToPackages.ps1 │ │ ├── MoveToPackages.ps1.meta │ │ ├── MoveToPackages_6.ps1 │ │ ├── MoveToPackages_6.ps1.meta │ │ ├── Plugins/ │ │ │ └── MongoDB.meta │ │ ├── Plugins.meta │ │ ├── package.json │ │ └── package.json.meta │ ├── com.halodi.halodi-unity-package-registry-manager/ │ │ ├── CHANGELOG.md │ │ ├── CHANGELOG.md.meta │ │ ├── Editor/ │ │ │ ├── Halodi/ │ │ │ │ ├── PackageRegistry/ │ │ │ │ │ ├── Core/ │ │ │ │ │ │ ├── CredentialManager.cs │ │ │ │ │ │ ├── CredentialManager.cs.meta │ │ │ │ │ │ ├── RegistryManager.cs │ │ │ │ │ │ ├── RegistryManager.cs.meta │ │ │ │ │ │ ├── ScopedRegistry.cs │ │ │ │ │ │ ├── ScopedRegistry.cs.meta │ │ │ │ │ │ ├── UpgradePackagesManager.cs │ │ │ │ │ │ └── UpgradePackagesManager.cs.meta │ │ │ │ │ ├── Core.meta │ │ │ │ │ ├── NPM/ │ │ │ │ │ │ ├── NPMLogin.cs │ │ │ │ │ │ ├── NPMLogin.cs.meta │ │ │ │ │ │ ├── NPMPublish.cs │ │ │ │ │ │ ├── NPMPublish.cs.meta │ │ │ │ │ │ ├── NPMResponse.cs │ │ │ │ │ │ ├── NPMResponse.cs.meta │ │ │ │ │ │ ├── PackageTarball.cs │ │ │ │ │ │ ├── PackageTarball.cs.meta │ │ │ │ │ │ ├── PublicationManifest.cs │ │ │ │ │ │ ├── PublicationManifest.cs.meta │ │ │ │ │ │ ├── WebExceptionParser.cs │ │ │ │ │ │ └── WebExceptionParser.cs.meta │ │ │ │ │ ├── NPM.meta │ │ │ │ │ ├── UI/ │ │ │ │ │ │ ├── BulkAddPackages.cs │ │ │ │ │ │ ├── BulkAddPackages.cs.meta │ │ │ │ │ │ ├── CredentialEditorView.cs │ │ │ │ │ │ ├── CredentialEditorView.cs.meta │ │ │ │ │ │ ├── CredentialManagerView.cs │ │ │ │ │ │ ├── CredentialManagerView.cs.meta │ │ │ │ │ │ ├── GetTokenView.cs │ │ │ │ │ │ ├── GetTokenView.cs.meta │ │ │ │ │ │ ├── InstallPackageCreator.cs │ │ │ │ │ │ ├── InstallPackageCreator.cs.meta │ │ │ │ │ │ ├── RegistryManagerView.cs │ │ │ │ │ │ ├── RegistryManagerView.cs.meta │ │ │ │ │ │ ├── ScopedRegistryEditorView.cs │ │ │ │ │ │ ├── ScopedRegistryEditorView.cs.meta │ │ │ │ │ │ ├── SettingsProvider.cs │ │ │ │ │ │ ├── SettingsProvider.cs.meta │ │ │ │ │ │ ├── UpgradePackagesView.cs │ │ │ │ │ │ └── UpgradePackagesView.cs.meta │ │ │ │ │ └── UI.meta │ │ │ │ └── PackageRegistry.meta │ │ │ ├── Halodi.PackageRegistryManager.Editor.asmdef │ │ │ ├── Halodi.PackageRegistryManager.Editor.asmdef.meta │ │ │ ├── Halodi.meta │ │ │ ├── ThirdParty/ │ │ │ │ ├── LICENSE.Tomlyn.md │ │ │ │ ├── LICENSE.Tomlyn.md.meta │ │ │ │ ├── README.md │ │ │ │ ├── README.md.meta │ │ │ │ ├── Tomlyn.dll.meta │ │ │ │ ├── Unity-SemVer/ │ │ │ │ │ ├── Artees.UnitySemVer.asmdef │ │ │ │ │ ├── Artees.UnitySemVer.asmdef.meta │ │ │ │ │ ├── CloudBuildManifest.cs │ │ │ │ │ ├── CloudBuildManifest.cs.meta │ │ │ │ │ ├── LICENSE │ │ │ │ │ ├── LICENSE.meta │ │ │ │ │ ├── README.md │ │ │ │ │ ├── README.md.meta │ │ │ │ │ ├── SemVer.cs │ │ │ │ │ ├── SemVer.cs.meta │ │ │ │ │ ├── SemVerAttribute.cs │ │ │ │ │ ├── SemVerAttribute.cs.meta │ │ │ │ │ ├── SemVerAutoBuild.cs │ │ │ │ │ ├── SemVerAutoBuild.cs.meta │ │ │ │ │ ├── SemVerComparer.cs │ │ │ │ │ ├── SemVerComparer.cs.meta │ │ │ │ │ ├── SemVerConverter.cs │ │ │ │ │ ├── SemVerConverter.cs.meta │ │ │ │ │ ├── SemVerErrorMessage.cs │ │ │ │ │ ├── SemVerErrorMessage.cs.meta │ │ │ │ │ ├── SemVerValidationResult.cs │ │ │ │ │ ├── SemVerValidationResult.cs.meta │ │ │ │ │ ├── SemVerValidator.cs │ │ │ │ │ └── SemVerValidator.cs.meta │ │ │ │ └── Unity-SemVer.meta │ │ │ └── ThirdParty.meta │ │ ├── Editor.meta │ │ ├── LICENSE.md │ │ ├── LICENSE.md.meta │ │ ├── README.md │ │ ├── README.md.meta │ │ ├── Tests/ │ │ │ ├── Editor/ │ │ │ │ ├── AssemblyInfo.cs │ │ │ │ ├── AssemblyInfo.cs.meta │ │ │ │ ├── Halodi.PackageRegistryManager.Tests.asmdef │ │ │ │ └── Halodi.PackageRegistryManager.Tests.asmdef.meta │ │ │ └── Editor.meta │ │ ├── Tests.meta │ │ ├── package.json │ │ └── package.json.meta │ ├── com.unity.ide.rider/ │ │ ├── .editorconfig │ │ ├── .signature │ │ ├── CHANGELOG.md │ │ ├── CHANGELOG.md.meta │ │ ├── CONTRIBUTING.md │ │ ├── CONTRIBUTING.md.meta │ │ ├── Documentation~/ │ │ │ ├── README.md │ │ │ ├── TableOfContents.md │ │ │ ├── index.md │ │ │ └── using-the-jetbrains-rider-editor-package.md │ │ ├── LICENSE.md │ │ ├── LICENSE.md.meta │ │ ├── Rider/ │ │ │ ├── Editor/ │ │ │ │ ├── Discovery.cs │ │ │ │ ├── Discovery.cs.meta │ │ │ │ ├── EditorPluginInterop.cs │ │ │ │ ├── EditorPluginInterop.cs.meta │ │ │ │ ├── JetBrains.Rider.PathLocator.dll.meta │ │ │ │ ├── LoggingLevel.cs │ │ │ │ ├── LoggingLevel.cs.meta │ │ │ │ ├── PluginSettings.cs │ │ │ │ ├── PluginSettings.cs.meta │ │ │ │ ├── PostProcessors/ │ │ │ │ │ ├── RiderAssetPostprocessor.cs │ │ │ │ │ └── RiderAssetPostprocessor.cs.meta │ │ │ │ ├── PostProcessors.meta │ │ │ │ ├── ProjectGeneration/ │ │ │ │ │ ├── AssemblyNameProvider.cs │ │ │ │ │ ├── AssemblyNameProvider.cs.meta │ │ │ │ │ ├── FileIOProvider.cs │ │ │ │ │ ├── FileIOProvider.cs.meta │ │ │ │ │ ├── GUIDProvider.cs │ │ │ │ │ ├── GUIDProvider.cs.meta │ │ │ │ │ ├── IAssemblyNameProvider.cs │ │ │ │ │ ├── IAssemblyNameProvider.cs.meta │ │ │ │ │ ├── IFileIO.cs │ │ │ │ │ ├── IFileIO.cs.meta │ │ │ │ │ ├── IGUIDGenerator.cs │ │ │ │ │ ├── IGUIDGenerator.cs.meta │ │ │ │ │ ├── IGenerator.cs │ │ │ │ │ ├── IGenerator.cs.meta │ │ │ │ │ ├── LastWriteTracker.cs │ │ │ │ │ ├── LastWriteTracker.cs.meta │ │ │ │ │ ├── PackageManagerTracker.cs │ │ │ │ │ ├── PackageManagerTracker.cs.meta │ │ │ │ │ ├── ProjectGeneration.cs │ │ │ │ │ ├── ProjectGeneration.cs.meta │ │ │ │ │ ├── ProjectGenerationFlag.cs │ │ │ │ │ ├── ProjectGenerationFlag.cs.meta │ │ │ │ │ ├── ProjectPart.cs │ │ │ │ │ ├── ProjectPart.cs.meta │ │ │ │ │ ├── SolutionGuidGenerator.cs │ │ │ │ │ └── SolutionGuidGenerator.cs.meta │ │ │ │ ├── ProjectGeneration.meta │ │ │ │ ├── Properties/ │ │ │ │ │ ├── AssemblyInfo.cs │ │ │ │ │ └── AssemblyInfo.cs.meta │ │ │ │ ├── Properties.meta │ │ │ │ ├── RiderInitializer.cs │ │ │ │ ├── RiderInitializer.cs.meta │ │ │ │ ├── RiderScriptEditor.cs │ │ │ │ ├── RiderScriptEditor.cs.meta │ │ │ │ ├── RiderScriptEditorData.cs │ │ │ │ ├── RiderScriptEditorData.cs.meta │ │ │ │ ├── RiderScriptEditorDataPersisted.cs │ │ │ │ ├── RiderScriptEditorDataPersisted.cs.meta │ │ │ │ ├── RiderStyles.cs │ │ │ │ ├── RiderStyles.cs.meta │ │ │ │ ├── StartUpMethodExecutor.cs │ │ │ │ ├── StartUpMethodExecutor.cs.meta │ │ │ │ ├── UnitTesting/ │ │ │ │ │ ├── CallbackData.cs │ │ │ │ │ ├── CallbackData.cs.meta │ │ │ │ │ ├── CallbackInitializer.cs │ │ │ │ │ ├── CallbackInitializer.cs.meta │ │ │ │ │ ├── RiderTestRunner.cs │ │ │ │ │ ├── RiderTestRunner.cs.meta │ │ │ │ │ ├── SyncTestRunCallback.cs │ │ │ │ │ ├── SyncTestRunCallback.cs.meta │ │ │ │ │ ├── SyncTestRunEventsHandler.cs │ │ │ │ │ ├── SyncTestRunEventsHandler.cs.meta │ │ │ │ │ ├── TestEvent.cs │ │ │ │ │ ├── TestEvent.cs.meta │ │ │ │ │ ├── TestsCallback.cs │ │ │ │ │ └── TestsCallback.cs.meta │ │ │ │ ├── UnitTesting.meta │ │ │ │ ├── Util/ │ │ │ │ │ ├── CommandLineParser.cs │ │ │ │ │ ├── CommandLineParser.cs.meta │ │ │ │ │ ├── FileSystemUtil.cs │ │ │ │ │ ├── FileSystemUtil.cs.meta │ │ │ │ │ ├── LibcNativeInterop.cs │ │ │ │ │ ├── LibcNativeInterop.cs.meta │ │ │ │ │ ├── RiderMenu.cs │ │ │ │ │ ├── RiderMenu.cs.meta │ │ │ │ │ ├── RiderPathUtil.cs │ │ │ │ │ ├── RiderPathUtil.cs.meta │ │ │ │ │ ├── SerializableVersion.cs │ │ │ │ │ ├── SerializableVersion.cs.meta │ │ │ │ │ ├── StringBuilderExtensions.cs │ │ │ │ │ ├── StringBuilderExtensions.cs.meta │ │ │ │ │ ├── StringUtils.cs │ │ │ │ │ └── StringUtils.cs.meta │ │ │ │ ├── Util.meta │ │ │ │ ├── com.unity.ide.rider.asmdef │ │ │ │ └── com.unity.ide.rider.asmdef.meta │ │ │ └── Editor.meta │ │ ├── Rider.meta │ │ ├── package.json │ │ └── package.json.meta │ ├── manifest.json │ └── packages-lock.json ├── ProjectSettings/ │ ├── AudioManager.asset │ ├── AutoStreamingSettings.asset │ ├── BurstAotSettings_Android.json │ ├── BurstAotSettings_StandaloneOSX.json │ ├── BurstAotSettings_StandaloneWindows.json │ ├── BurstAotSettings_iOS.json │ ├── ClusterInputManager.asset │ ├── CommonBurstAotSettings.json │ ├── DynamicsManager.asset │ ├── EditorBuildSettings.asset │ ├── EditorSettings.asset │ ├── GraphicsSettings.asset │ ├── HybridCLRSettings.asset │ ├── InputManager.asset │ ├── MemorySettings.asset │ ├── MultiplayerManager.asset │ ├── NavMeshAreas.asset │ ├── NavMeshLayers.asset │ ├── NetworkManager.asset │ ├── PackageManagerSettings.asset │ ├── Physics2DSettings.asset │ ├── PresetManager.asset │ ├── ProjectSettings.asset │ ├── ProjectVersion.txt │ ├── QualitySettings.asset │ ├── SceneTemplateSettings.json │ ├── ShaderGraphSettings.asset │ ├── TagManager.asset │ ├── TimeManager.asset │ ├── TimelineSettings.asset │ ├── URPProjectSettings.asset │ ├── UnityConnectSettings.asset │ ├── VFXManager.asset │ ├── VersionControlSettings.asset │ └── XRSettings.asset ├── README.md ├── Scripts/ │ └── Publish-linux-x64.ps1 ├── Unity.sln.DotSettings └── Unity.userprefs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ ############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto ############################################################################### # Set default behavior for command prompt diff. # # This is need for earlier builds of msysgit that does not have it on by # default for csharp files. # Note: This is only used by command line ############################################################################### #*.cs diff=csharp ############################################################################### # Set the merge driver for project and solution files # # Merging from the command prompt will add diff markers to the files if there # are conflicts (Merging from VS is not affected by the settings below, in VS # the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below ############################################################################### #*.sln merge=binary #*.csproj merge=binary #*.vbproj merge=binary #*.vcxproj merge=binary #*.vcproj merge=binary #*.dbproj merge=binary #*.fsproj merge=binary #*.lsproj merge=binary #*.wixproj merge=binary #*.modelproj merge=binary #*.sqlproj merge=binary #*.wwaproj merge=binary ############################################################################### # behavior for image files # # image files are treated as binary by default. ############################################################################### #*.jpg binary #*.png binary #*.gif binary ############################################################################### # diff behavior for common document formats # # Convert binary document formats to text before diffing them. This feature # is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### #*.doc diff=astextplain #*.DOC diff=astextplain #*.docx diff=astextplain #*.DOCX diff=astextplain #*.dot diff=astextplain #*.DOT diff=astextplain #*.pdf diff=astextplain #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain ================================================ FILE: .gitignore ================================================ #IDE文件 .vs/ .idea/ .vscode/ _ReSharper.CSharp/ #svn */.svn/ .svn/ #Excel的临时文件 ~$*.xlsx ~$*.xlsx.meta #Unity相关目录 .gradle Bundles Library Logs UserSettings /*.csproj *.vsconfig ProjectSettings/RiderScriptEditorPersistedState.asset HybridCLRData #Unity/Assets Assets/Bundles/Code/* Assets/Bundles/AotDlls/* Assets/StreamingAssets/ Assets/StreamingAssets.meta #其他 .objs/ .DS_Store /*.user bin/ Bin/ /Log /Logs /**/obj/ /obj/ /Release /Temp /Tools/MongoDB Publish/ */Temp/ */TestResults/ Assets/Generate/Model/Proto* Assets/Generate/Model/Excel* MongoDB/ Unity.sln Unity.sln.DotSettings.user /ET.sln Assets/Plugins/Sirenix* Assets/HybridCLR* ================================================ FILE: Assets/.gitignore ================================================ ================================================ FILE: Assets/DefaultVolumeProfile.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!114 &-8794448539801599142 MonoBehaviour: m_ObjectHideFlags: 3 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: fb60a22f311433c4c962b888d1393f88, type: 3} m_Name: PaniniProjection m_EditorClassIdentifier: active: 1 distance: m_OverrideState: 1 m_Value: 0 cropToFit: m_OverrideState: 1 m_Value: 1 --- !u!114 &-8291749687215229861 MonoBehaviour: m_ObjectHideFlags: 3 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 221518ef91623a7438a71fef23660601, type: 3} m_Name: WhiteBalance m_EditorClassIdentifier: active: 1 temperature: m_OverrideState: 1 m_Value: 0 tint: m_OverrideState: 1 m_Value: 0 --- !u!114 &-7181315038608799321 MonoBehaviour: m_ObjectHideFlags: 3 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 70afe9e12c7a7ed47911bb608a23a8ff, type: 3} m_Name: SplitToning m_EditorClassIdentifier: active: 1 shadows: m_OverrideState: 1 m_Value: {r: 0.5, g: 0.5, b: 0.5, a: 1} highlights: m_OverrideState: 1 m_Value: {r: 0.5, g: 0.5, b: 0.5, a: 1} balance: m_OverrideState: 1 m_Value: 0 --- !u!114 &-6873332034207124496 MonoBehaviour: m_ObjectHideFlags: 3 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 97c23e3b12dc18c42a140437e53d3951, type: 3} m_Name: Tonemapping m_EditorClassIdentifier: active: 1 mode: m_OverrideState: 1 m_Value: 0 neutralHDRRangeReductionMode: m_OverrideState: 1 m_Value: 2 acesPreset: m_OverrideState: 1 m_Value: 3 hueShiftAmount: m_OverrideState: 1 m_Value: 0 detectPaperWhite: m_OverrideState: 1 m_Value: 0 paperWhite: m_OverrideState: 1 m_Value: 300 detectBrightnessLimits: m_OverrideState: 1 m_Value: 1 minNits: m_OverrideState: 1 m_Value: 0.005 maxNits: m_OverrideState: 1 m_Value: 1000 --- !u!114 &-6855864280655889699 MonoBehaviour: m_ObjectHideFlags: 3 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 66f335fb1ffd8684294ad653bf1c7564, type: 3} m_Name: ColorAdjustments m_EditorClassIdentifier: active: 1 postExposure: m_OverrideState: 1 m_Value: 0 contrast: m_OverrideState: 1 m_Value: 0 colorFilter: m_OverrideState: 1 m_Value: {r: 1, g: 1, b: 1, a: 1} hueShift: m_OverrideState: 1 m_Value: 0 saturation: m_OverrideState: 1 m_Value: 0 --- !u!114 &-5961836973711844057 MonoBehaviour: m_ObjectHideFlags: 3 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 81180773991d8724ab7f2d216912b564, type: 3} m_Name: ChromaticAberration m_EditorClassIdentifier: active: 1 intensity: m_OverrideState: 1 m_Value: 0 --- !u!114 &-4786318987195808616 MonoBehaviour: m_ObjectHideFlags: 3 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: e021b4c809a781e468c2988c016ebbea, type: 3} m_Name: ColorLookup m_EditorClassIdentifier: active: 1 texture: m_OverrideState: 1 m_Value: {fileID: 0} dimension: 1 contribution: m_OverrideState: 1 m_Value: 0 --- !u!114 &-4262818703771780653 MonoBehaviour: m_ObjectHideFlags: 3 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 29fa0085f50d5e54f8144f766051a691, type: 3} m_Name: FilmGrain m_EditorClassIdentifier: active: 1 type: m_OverrideState: 1 m_Value: 0 intensity: m_OverrideState: 1 m_Value: 0 response: m_OverrideState: 1 m_Value: 0.8 texture: m_OverrideState: 1 m_Value: {fileID: 0} --- !u!114 &-3017658206072825247 MonoBehaviour: m_ObjectHideFlags: 3 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 06437c1ff663d574d9447842ba0a72e4, type: 3} m_Name: ScreenSpaceLensFlare m_EditorClassIdentifier: active: 1 intensity: m_OverrideState: 1 m_Value: 0 tintColor: m_OverrideState: 1 m_Value: {r: 1, g: 1, b: 1, a: 1} bloomMip: m_OverrideState: 1 m_Value: 1 firstFlareIntensity: m_OverrideState: 1 m_Value: 1 secondaryFlareIntensity: m_OverrideState: 1 m_Value: 1 warpedFlareIntensity: m_OverrideState: 1 m_Value: 1 warpedFlareScale: m_OverrideState: 1 m_Value: {x: 1, y: 1} samples: m_OverrideState: 1 m_Value: 1 sampleDimmer: m_OverrideState: 1 m_Value: 0.5 vignetteEffect: m_OverrideState: 1 m_Value: 1 startingPosition: m_OverrideState: 1 m_Value: 1.25 scale: m_OverrideState: 1 m_Value: 1.5 streaksIntensity: m_OverrideState: 1 m_Value: 0 streaksLength: m_OverrideState: 1 m_Value: 0.5 streaksOrientation: m_OverrideState: 1 m_Value: 0 streaksThreshold: m_OverrideState: 1 m_Value: 0.25 resolution: m_OverrideState: 1 m_Value: 4 chromaticAbberationIntensity: m_OverrideState: 1 m_Value: 0.5 --- !u!114 &-2991397607662196306 MonoBehaviour: m_ObjectHideFlags: 3 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: c01700fd266d6914ababb731e09af2eb, type: 3} m_Name: DepthOfField m_EditorClassIdentifier: active: 1 mode: m_OverrideState: 1 m_Value: 0 gaussianStart: m_OverrideState: 1 m_Value: 10 gaussianEnd: m_OverrideState: 1 m_Value: 30 gaussianMaxRadius: m_OverrideState: 1 m_Value: 1 highQualitySampling: m_OverrideState: 1 m_Value: 0 focusDistance: m_OverrideState: 1 m_Value: 10 aperture: m_OverrideState: 1 m_Value: 5.6 focalLength: m_OverrideState: 1 m_Value: 50 bladeCount: m_OverrideState: 1 m_Value: 5 bladeCurvature: m_OverrideState: 1 m_Value: 1 bladeRotation: m_OverrideState: 1 m_Value: 0 --- !u!114 &-2399972201891767753 MonoBehaviour: m_ObjectHideFlags: 3 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: ccf1aba9553839d41ae37dd52e9ebcce, type: 3} m_Name: MotionBlur m_EditorClassIdentifier: active: 1 mode: m_OverrideState: 1 m_Value: 0 quality: m_OverrideState: 1 m_Value: 0 intensity: m_OverrideState: 1 m_Value: 0 clamp: m_OverrideState: 1 m_Value: 0.05 --- !u!114 &-1966137642871919569 MonoBehaviour: m_ObjectHideFlags: 3 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 3eb4b772797da9440885e8bd939e9560, type: 3} m_Name: ColorCurves m_EditorClassIdentifier: active: 1 master: m_OverrideState: 1 m_Value: k__BackingField: 2 m_Loop: 0 m_ZeroValue: 0 m_Range: 1 m_Curve: serializedVersion: 2 m_Curve: - serializedVersion: 3 time: 0 value: 0 inSlope: 1 outSlope: 1 tangentMode: 0 weightedMode: 0 inWeight: 0 outWeight: 0 - serializedVersion: 3 time: 1 value: 1 inSlope: 1 outSlope: 1 tangentMode: 0 weightedMode: 0 inWeight: 0 outWeight: 0 m_PreInfinity: 2 m_PostInfinity: 2 m_RotationOrder: 4 red: m_OverrideState: 1 m_Value: k__BackingField: 2 m_Loop: 0 m_ZeroValue: 0 m_Range: 1 m_Curve: serializedVersion: 2 m_Curve: - serializedVersion: 3 time: 0 value: 0 inSlope: 1 outSlope: 1 tangentMode: 0 weightedMode: 0 inWeight: 0 outWeight: 0 - serializedVersion: 3 time: 1 value: 1 inSlope: 1 outSlope: 1 tangentMode: 0 weightedMode: 0 inWeight: 0 outWeight: 0 m_PreInfinity: 2 m_PostInfinity: 2 m_RotationOrder: 4 green: m_OverrideState: 1 m_Value: k__BackingField: 2 m_Loop: 0 m_ZeroValue: 0 m_Range: 1 m_Curve: serializedVersion: 2 m_Curve: - serializedVersion: 3 time: 0 value: 0 inSlope: 1 outSlope: 1 tangentMode: 0 weightedMode: 0 inWeight: 0 outWeight: 0 - serializedVersion: 3 time: 1 value: 1 inSlope: 1 outSlope: 1 tangentMode: 0 weightedMode: 0 inWeight: 0 outWeight: 0 m_PreInfinity: 2 m_PostInfinity: 2 m_RotationOrder: 4 blue: m_OverrideState: 1 m_Value: k__BackingField: 2 m_Loop: 0 m_ZeroValue: 0 m_Range: 1 m_Curve: serializedVersion: 2 m_Curve: - serializedVersion: 3 time: 0 value: 0 inSlope: 1 outSlope: 1 tangentMode: 0 weightedMode: 0 inWeight: 0 outWeight: 0 - serializedVersion: 3 time: 1 value: 1 inSlope: 1 outSlope: 1 tangentMode: 0 weightedMode: 0 inWeight: 0 outWeight: 0 m_PreInfinity: 2 m_PostInfinity: 2 m_RotationOrder: 4 hueVsHue: m_OverrideState: 1 m_Value: k__BackingField: 0 m_Loop: 1 m_ZeroValue: 0.5 m_Range: 1 m_Curve: serializedVersion: 2 m_Curve: [] m_PreInfinity: 2 m_PostInfinity: 2 m_RotationOrder: 4 hueVsSat: m_OverrideState: 1 m_Value: k__BackingField: 0 m_Loop: 1 m_ZeroValue: 0.5 m_Range: 1 m_Curve: serializedVersion: 2 m_Curve: [] m_PreInfinity: 2 m_PostInfinity: 2 m_RotationOrder: 4 satVsSat: m_OverrideState: 1 m_Value: k__BackingField: 0 m_Loop: 0 m_ZeroValue: 0.5 m_Range: 1 m_Curve: serializedVersion: 2 m_Curve: [] m_PreInfinity: 2 m_PostInfinity: 2 m_RotationOrder: 4 lumVsSat: m_OverrideState: 1 m_Value: k__BackingField: 0 m_Loop: 0 m_ZeroValue: 0.5 m_Range: 1 m_Curve: serializedVersion: 2 m_Curve: [] m_PreInfinity: 2 m_PostInfinity: 2 m_RotationOrder: 4 --- !u!114 &-596686748398721985 MonoBehaviour: m_ObjectHideFlags: 3 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 5485954d14dfb9a4c8ead8edb0ded5b1, type: 3} m_Name: LiftGammaGain m_EditorClassIdentifier: active: 1 lift: m_OverrideState: 1 m_Value: {x: 1, y: 1, z: 1, w: 0} gamma: m_OverrideState: 1 m_Value: {x: 1, y: 1, z: 1, w: 0} gain: m_OverrideState: 1 m_Value: {x: 1, y: 1, z: 1, w: 0} --- !u!114 &-280383500228153280 MonoBehaviour: m_ObjectHideFlags: 3 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 558a8e2b6826cf840aae193990ba9f2e, type: 3} m_Name: ShadowsMidtonesHighlights m_EditorClassIdentifier: active: 1 shadows: m_OverrideState: 1 m_Value: {x: 1, y: 1, z: 1, w: 0} midtones: m_OverrideState: 1 m_Value: {x: 1, y: 1, z: 1, w: 0} highlights: m_OverrideState: 1 m_Value: {x: 1, y: 1, z: 1, w: 0} shadowsStart: m_OverrideState: 1 m_Value: 0 shadowsEnd: m_OverrideState: 1 m_Value: 0.3 highlightsStart: m_OverrideState: 1 m_Value: 0.55 highlightsEnd: m_OverrideState: 1 m_Value: 1 --- !u!114 &11400000 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: d7fd9488000d3734a9e00ee676215985, type: 3} m_Name: DefaultVolumeProfile m_EditorClassIdentifier: components: - {fileID: -5961836973711844057} - {fileID: -4786318987195808616} - {fileID: -8291749687215229861} - {fileID: -2399972201891767753} - {fileID: -6873332034207124496} - {fileID: -280383500228153280} - {fileID: -2991397607662196306} - {fileID: -6855864280655889699} - {fileID: 2385114419025452938} - {fileID: 5686534837009777132} - {fileID: -596686748398721985} - {fileID: 8045345597058296069} - {fileID: -1966137642871919569} - {fileID: -8794448539801599142} - {fileID: -4262818703771780653} - {fileID: 4746937895915073762} - {fileID: -3017658206072825247} - {fileID: -7181315038608799321} - {fileID: 367049241085752711} --- !u!114 &367049241085752711 MonoBehaviour: m_ObjectHideFlags: 3 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 6bd486065ce11414fa40e631affc4900, type: 3} m_Name: ProbeVolumesOptions m_EditorClassIdentifier: active: 1 normalBias: m_OverrideState: 1 m_Value: 0.05 viewBias: m_OverrideState: 1 m_Value: 0.1 scaleBiasWithMinProbeDistance: m_OverrideState: 1 m_Value: 0 samplingNoise: m_OverrideState: 1 m_Value: 0.1 animateSamplingNoise: m_OverrideState: 1 m_Value: 1 leakReductionMode: m_OverrideState: 1 m_Value: 2 minValidDotProductValue: m_OverrideState: 1 m_Value: 0.1 occlusionOnlyReflectionNormalization: m_OverrideState: 1 m_Value: 1 intensityMultiplier: m_OverrideState: 1 m_Value: 1 skyOcclusionIntensityMultiplier: m_OverrideState: 1 m_Value: 1 worldOffset: m_OverrideState: 1 m_Value: {x: 0, y: 0, z: 0} --- !u!114 &2385114419025452938 MonoBehaviour: m_ObjectHideFlags: 3 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 899c54efeace73346a0a16faa3afe726, type: 3} m_Name: Vignette m_EditorClassIdentifier: active: 1 color: m_OverrideState: 1 m_Value: {r: 0, g: 0, b: 0, a: 1} center: m_OverrideState: 1 m_Value: {x: 0.5, y: 0.5} intensity: m_OverrideState: 1 m_Value: 0 smoothness: m_OverrideState: 1 m_Value: 0.2 rounded: m_OverrideState: 1 m_Value: 0 --- !u!114 &4746937895915073762 MonoBehaviour: m_ObjectHideFlags: 3 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: c5e1dc532bcb41949b58bc4f2abfbb7e, type: 3} m_Name: LensDistortion m_EditorClassIdentifier: active: 1 intensity: m_OverrideState: 1 m_Value: 0 xMultiplier: m_OverrideState: 1 m_Value: 1 yMultiplier: m_OverrideState: 1 m_Value: 1 center: m_OverrideState: 1 m_Value: {x: 0.5, y: 0.5} scale: m_OverrideState: 1 m_Value: 1 --- !u!114 &5686534837009777132 MonoBehaviour: m_ObjectHideFlags: 3 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: cdfbdbb87d3286943a057f7791b43141, type: 3} m_Name: ChannelMixer m_EditorClassIdentifier: active: 1 redOutRedIn: m_OverrideState: 1 m_Value: 100 redOutGreenIn: m_OverrideState: 1 m_Value: 0 redOutBlueIn: m_OverrideState: 1 m_Value: 0 greenOutRedIn: m_OverrideState: 1 m_Value: 0 greenOutGreenIn: m_OverrideState: 1 m_Value: 100 greenOutBlueIn: m_OverrideState: 1 m_Value: 0 blueOutRedIn: m_OverrideState: 1 m_Value: 0 blueOutGreenIn: m_OverrideState: 1 m_Value: 0 blueOutBlueIn: m_OverrideState: 1 m_Value: 100 --- !u!114 &8045345597058296069 MonoBehaviour: m_ObjectHideFlags: 3 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 0b2db86121404754db890f4c8dfe81b2, type: 3} m_Name: Bloom m_EditorClassIdentifier: active: 1 skipIterations: m_OverrideState: 1 m_Value: 1 threshold: m_OverrideState: 1 m_Value: 0.9 intensity: m_OverrideState: 1 m_Value: 0 scatter: m_OverrideState: 1 m_Value: 0.7 clamp: m_OverrideState: 1 m_Value: 65472 tint: m_OverrideState: 1 m_Value: {r: 1, g: 1, b: 1, a: 1} highQualityFiltering: m_OverrideState: 1 m_Value: 0 downscale: m_OverrideState: 1 m_Value: 0 maxIterations: m_OverrideState: 1 m_Value: 6 dirtTexture: m_OverrideState: 1 m_Value: {fileID: 0} dimension: 1 dirtIntensity: m_OverrideState: 1 m_Value: 0 ================================================ FILE: Assets/DefaultVolumeProfile.asset.meta ================================================ fileFormatVersion: 2 guid: a722862efbc47421c908e1e722eba667 NativeFormatImporter: externalObjects: {} mainObjectFileID: 11400000 userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Resources/BuildinFileManifest.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!114 &11400000 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 71b02dfa7aa9d4545b3417a18477fbee, type: 3} m_Name: BuildinFileManifest m_EditorClassIdentifier: BuildinFiles: - PackageName: DefaultPackage FileName: 147968a4c6cd7a95181e4a092e5e10c6.bundle FileCRC32: 567ffa54 - PackageName: DefaultPackage FileName: 3425f94f739fe98d1bd89c0749aa8e11.bundle FileCRC32: 0907b096 - PackageName: DefaultPackage FileName: 4c3e0c50717e8bfd5834f4a8b61e3fd6.bundle FileCRC32: c683628e - PackageName: DefaultPackage FileName: 56b89de42fec4292516c2328bf5aca8b.bundle FileCRC32: 7af32f12 - PackageName: DefaultPackage FileName: 5afeb41166a6d809ce8786e74f717023.bundle FileCRC32: 8540f538 - PackageName: DefaultPackage FileName: 7ac619fc67603865d284ea164d1080ff.bundle FileCRC32: 61ec1557 - PackageName: DefaultPackage FileName: 87d68d5fe5c55aebff3c1fe2faed2c62.bundle FileCRC32: 0939a372 - PackageName: DefaultPackage FileName: 954000280943584f2a8faacc0792bf58.bundle FileCRC32: 2db2f125 - PackageName: DefaultPackage FileName: 9dd6926894f6017b8e6cbd3da83aa6e6.bundle FileCRC32: 9843eba6 - PackageName: DefaultPackage FileName: a754b26c34c740e479f5a29193683718.bundle FileCRC32: ffd1a30c - PackageName: DefaultPackage FileName: b2844a6487361a48b9f0b7b4ffcfe5a3.bundle FileCRC32: c44328ce - PackageName: DefaultPackage FileName: bedbd2c117105ef4a813f64810402a44.bundle FileCRC32: 75fb7c9c - PackageName: DefaultPackage FileName: c0e42266a4d36a5145f8dda2cad647a3.bundle FileCRC32: a0318fcc - PackageName: DefaultPackage FileName: e06279c788626e8310d94d58ef235d49.bundle FileCRC32: e1678ed4 - PackageName: DefaultPackage FileName: e244fff3d60759e1e3f614edbfeaf7d1.bundle FileCRC32: de496837 - PackageName: DefaultPackage FileName: e3103bccba7a78765e30c8008337fc2a.bundle FileCRC32: b5c8ab15 - PackageName: DefaultPackage FileName: e505d3499c990c551faee4f4f8c90ff0.bundle FileCRC32: 48237dd9 - PackageName: DefaultPackage FileName: ef8675f933b496cc8967585a1d485311.bundle FileCRC32: 6bae558c ================================================ FILE: Assets/Resources/BuildinFileManifest.asset.meta ================================================ fileFormatVersion: 2 guid: e70bfb3ccac3b4f43af72c0a8b8cdd63 NativeFormatImporter: externalObjects: {} mainObjectFileID: 11400000 userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Resources.meta ================================================ fileFormatVersion: 2 guid: fe14f6ff4d78a464fbc6dd761d73e38e folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Settings/Build Profiles/Mac.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!114 &11400000 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 15003, guid: 0000000000000000e000000000000000, type: 0} m_Name: Mac m_EditorClassIdentifier: m_AssetVersion: 1 m_BuildTarget: 2 m_Subtarget: 2 m_PlatformId: 0d2129357eac403d8b359c2dcbf82502 m_PlatformBuildProfile: rid: 6418588523325555037 m_OverrideGlobalSceneList: 0 m_Scenes: [] m_ScriptingDefines: [] m_PlayerSettingsYaml: m_Settings: - line: '| PlayerSettings:' - line: '| m_ObjectHideFlags: 0' - line: '| serializedVersion: 28' - line: '| productGUID: 1eb5b450436966b49a962ffe89a9d776' - line: '| AndroidProfiler: 0' - line: '| AndroidFilterTouchesWhenObscured: 0' - line: '| AndroidEnableSustainedPerformanceMode: 0' - line: '| defaultScreenOrientation: 4' - line: '| targetDevice: 2' - line: '| useOnDemandResources: 0' - line: '| accelerometerFrequency: 60' - line: '| companyName: test' - line: '| productName: ET' - line: '| defaultCursor: {instanceID: 0}' - line: '| cursorHotspot: {x: 0, y: 0}' - line: '| m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1}' - line: '| m_ShowUnitySplashScreen: 1' - line: '| m_ShowUnitySplashLogo: 1' - line: '| m_SplashScreenOverlayOpacity: 1' - line: '| m_SplashScreenAnimation: 1' - line: '| m_SplashScreenLogoStyle: 1' - line: '| m_SplashScreenDrawMode: 0' - line: '| m_SplashScreenBackgroundAnimationZoom: 1' - line: '| m_SplashScreenLogoAnimationZoom: 1' - line: '| m_SplashScreenBackgroundLandscapeAspect: 1' - line: '| m_SplashScreenBackgroundPortraitAspect: 1' - line: '| m_SplashScreenBackgroundLandscapeUvs:' - line: '| serializedVersion: 2' - line: '| x: 0' - line: '| y: 0' - line: '| width: 1' - line: '| height: 1' - line: '| m_SplashScreenBackgroundPortraitUvs:' - line: '| serializedVersion: 2' - line: '| x: 0' - line: '| y: 0' - line: '| width: 1' - line: '| height: 1' - line: '| m_SplashScreenLogos: []' - line: '| m_VirtualRealitySplashScreen: {instanceID: 0}' - line: '| m_HolographicTrackingLossScreen: {instanceID: 0}' - line: '| defaultScreenWidth: 1024' - line: '| defaultScreenHeight: 768' - line: '| defaultScreenWidthWeb: 960' - line: '| defaultScreenHeightWeb: 600' - line: '| m_StereoRenderingPath: 0' - line: '| m_ActiveColorSpace: 1' - line: '| unsupportedMSAAFallback: 0' - line: '| m_SpriteBatchMaxVertexCount: 65535' - line: '| m_SpriteBatchVertexThreshold: 300' - line: '| m_MTRendering: 1' - line: '| mipStripping: 0' - line: '| numberOfMipsStripped: 0' - line: '| numberOfMipsStrippedPerMipmapLimitGroup: {}' - line: '| m_StackTraceTypes: 010000000100000001000000010000000100000001000000' - line: '| iosShowActivityIndicatorOnLoading: -1' - line: '| androidShowActivityIndicatorOnLoading: -1' - line: '| iosUseCustomAppBackgroundBehavior: 0' - line: '| allowedAutorotateToPortrait: 1' - line: '| allowedAutorotateToPortraitUpsideDown: 1' - line: '| allowedAutorotateToLandscapeRight: 1' - line: '| allowedAutorotateToLandscapeLeft: 1' - line: '| useOSAutorotation: 1' - line: '| use32BitDisplayBuffer: 1' - line: '| preserveFramebufferAlpha: 0' - line: '| disableDepthAndStencilBuffers: 0' - line: '| androidStartInFullscreen: 1' - line: '| androidRenderOutsideSafeArea: 0' - line: '| androidUseSwappy: 0' - line: '| androidBlitType: 0' - line: '| androidResizeableActivity: 0' - line: '| androidDefaultWindowWidth: 1920' - line: '| androidDefaultWindowHeight: 1080' - line: '| androidMinimumWindowWidth: 400' - line: '| androidMinimumWindowHeight: 300' - line: '| androidFullscreenMode: 1' - line: '| androidAutoRotationBehavior: 1' - line: '| androidPredictiveBackSupport: 0' - line: '| androidApplicationEntry: 1' - line: '| defaultIsNativeResolution: 1' - line: '| macRetinaSupport: 1' - line: '| runInBackground: 1' - line: '| muteOtherAudioSources: 0' - line: '| Prepare IOS For Recording: 0' - line: '| Force IOS Speakers When Recording: 0' - line: '| deferSystemGesturesMode: 0' - line: '| hideHomeButton: 0' - line: '| submitAnalytics: 1' - line: '| usePlayerLog: 1' - line: '| dedicatedServerOptimizations: 0' - line: '| bakeCollisionMeshes: 0' - line: '| forceSingleInstance: 0' - line: '| useFlipModelSwapchain: 1' - line: '| resizableWindow: 0' - line: '| useMacAppStoreValidation: 0' - line: '| macAppStoreCategory: public.app-category.games' - line: '| gpuSkinning: 0' - line: '| meshDeformation: 0' - line: '| xboxPIXTextureCapture: 0' - line: '| xboxEnableAvatar: 0' - line: '| xboxEnableKinect: 0' - line: '| xboxEnableKinectAutoTracking: 0' - line: '| xboxEnableFitness: 0' - line: '| visibleInBackground: 0' - line: '| allowFullscreenSwitch: 1' - line: '| fullscreenMode: 3' - line: '| xboxSpeechDB: 0' - line: '| xboxEnableHeadOrientation: 0' - line: '| xboxEnableGuest: 0' - line: '| xboxEnablePIXSampling: 0' - line: '| metalFramebufferOnly: 0' - line: '| xboxOneResolution: 0' - line: '| xboxOneSResolution: 0' - line: '| xboxOneXResolution: 3' - line: '| xboxOneMonoLoggingLevel: 0' - line: '| xboxOneLoggingLevel: 1' - line: '| xboxOneDisableEsram: 0' - line: '| xboxOneEnableTypeOptimization: 0' - line: '| xboxOnePresentImmediateThreshold: 0' - line: '| switchQueueCommandMemory: 0' - line: '| switchQueueControlMemory: 16384' - line: '| switchQueueComputeMemory: 262144' - line: '| switchNVNShaderPoolsGranularity: 33554432' - line: '| switchNVNDefaultPoolsGranularity: 16777216' - line: '| switchNVNOtherPoolsGranularity: 16777216' - line: '| switchGpuScratchPoolGranularity: 2097152' - line: '| switchAllowGpuScratchShrinking: 0' - line: '| switchNVNMaxPublicTextureIDCount: 0' - line: '| switchNVNMaxPublicSamplerIDCount: 0' - line: '| switchMaxWorkerMultiple: 8' - line: '| switchNVNGraphicsFirmwareMemory: 32' - line: '| vulkanNumSwapchainBuffers: 3' - line: '| vulkanEnableSetSRGBWrite: 0' - line: '| vulkanEnablePreTransform: 0' - line: '| vulkanEnableLateAcquireNextImage: 0' - line: '| vulkanEnableCommandBufferRecycling: 1' - line: '| loadStoreDebugModeEnabled: 0' - line: '| visionOSBundleVersion: 1.0' - line: '| tvOSBundleVersion: 1.0' - line: '| bundleVersion: 1.0' - line: '| preloadedAssets: []' - line: '| metroInputSource: 0' - line: '| wsaTransparentSwapchain: 0' - line: '| m_HolographicPauseOnTrackingLoss: 1' - line: '| xboxOneDisableKinectGpuReservation: 0' - line: '| xboxOneEnable7thCore: 0' - line: '| vrSettings:' - line: '| enable360StereoCapture: 0' - line: '| isWsaHolographicRemotingEnabled: 0' - line: '| enableFrameTimingStats: 0' - line: '| enableOpenGLProfilerGPURecorders: 1' - line: '| allowHDRDisplaySupport: 0' - line: '| useHDRDisplay: 0' - line: '| hdrBitDepth: 0' - line: '| m_ColorGamuts: 00000000' - line: '| targetPixelDensity: 30' - line: '| resolutionScalingMode: 0' - line: '| resetResolutionOnWindowResize: 0' - line: '| androidSupportedAspectRatio: 1' - line: '| androidMaxAspectRatio: 2.1' - line: '| androidMinAspectRatio: 1' - line: '| applicationIdentifier:' - line: '| Android: com.test.ET' - line: '| Standalone: com.test.ET' - line: '| Tizen: com.Company.ProductName' - line: '| iPhone: com.test.ET' - line: '| tvOS: com.Company.ProductName' - line: '| buildNumber:' - line: '| Standalone: 0' - line: '| VisionOS: 0' - line: '| iPhone: 0' - line: '| tvOS: 0' - line: '| overrideDefaultApplicationIdentifier: 0' - line: '| AndroidBundleVersionCode: 1' - line: '| AndroidMinSdkVersion: 23' - line: '| AndroidTargetSdkVersion: 0' - line: '| AndroidPreferredInstallLocation: 1' - line: '| aotOptions: ' - line: '| stripEngineCode: 0' - line: '| iPhoneStrippingLevel: 0' - line: '| iPhoneScriptCallOptimization: 0' - line: '| ForceInternetPermission: 0' - line: '| ForceSDCardPermission: 0' - line: '| CreateWallpaper: 0' - line: '| androidSplitApplicationBinary: 0' - line: '| keepLoadedShadersAlive: 0' - line: '| StripUnusedMeshComponents: 0' - line: '| strictShaderVariantMatching: 0' - line: '| VertexChannelCompressionMask: 214' - line: '| iPhoneSdkVersion: 988' - line: '| iOSSimulatorArchitecture: 0' - line: '| iOSTargetOSVersionString: 13.0' - line: '| tvOSSdkVersion: 0' - line: '| tvOSSimulatorArchitecture: 0' - line: '| tvOSRequireExtendedGameController: 0' - line: '| tvOSTargetOSVersionString: 13.0' - line: '| VisionOSSdkVersion: 0' - line: '| VisionOSTargetOSVersionString: 1.0' - line: '| uIPrerenderedIcon: 0' - line: '| uIRequiresPersistentWiFi: 0' - line: '| uIRequiresFullScreen: 1' - line: '| uIStatusBarHidden: 1' - line: '| uIExitOnSuspend: 0' - line: '| uIStatusBarStyle: 0' - line: '| appleTVSplashScreen: {instanceID: 0}' - line: '| appleTVSplashScreen2x: {instanceID: 0}' - line: '| tvOSSmallIconLayers: []' - line: '| tvOSSmallIconLayers2x: []' - line: '| tvOSLargeIconLayers: []' - line: '| tvOSLargeIconLayers2x: []' - line: '| tvOSTopShelfImageLayers: []' - line: '| tvOSTopShelfImageLayers2x: []' - line: '| tvOSTopShelfImageWideLayers: []' - line: '| tvOSTopShelfImageWideLayers2x: []' - line: '| iOSLaunchScreenType: 0' - line: '| iOSLaunchScreenPortrait: {instanceID: 0}' - line: '| iOSLaunchScreenLandscape: {instanceID: 0}' - line: '| iOSLaunchScreenBackgroundColor:' - line: '| serializedVersion: 2' - line: '| rgba: 0' - line: '| iOSLaunchScreenFillPct: 100' - line: '| iOSLaunchScreenSize: 100' - line: '| iOSLaunchScreeniPadType: 0' - line: '| iOSLaunchScreeniPadImage: {instanceID: 0}' - line: '| iOSLaunchScreeniPadBackgroundColor:' - line: '| serializedVersion: 2' - line: '| rgba: 0' - line: '| iOSLaunchScreeniPadFillPct: 100' - line: '| iOSLaunchScreeniPadSize: 100' - line: '| iOSLaunchScreenCustomStoryboardPath: ' - line: '| iOSLaunchScreeniPadCustomStoryboardPath: ' - line: '| iOSDeviceRequirements: []' - line: '| iOSURLSchemes: []' - line: '| macOSURLSchemes: []' - line: '| iOSBackgroundModes: 0' - line: '| iOSMetalForceHardShadows: 0' - line: '| metalEditorSupport: 1' - line: '| metalAPIValidation: 1' - line: '| metalCompileShaderBinary: 0' - line: '| iOSRenderExtraFrameOnPause: 1' - line: '| iosCopyPluginsCodeInsteadOfSymlink: 0' - line: '| appleDeveloperTeamID: ' - line: '| iOSManualSigningProvisioningProfileID: ' - line: '| tvOSManualSigningProvisioningProfileID: ' - line: '| VisionOSManualSigningProvisioningProfileID: ' - line: '| iOSManualSigningProvisioningProfileType: 0' - line: '| tvOSManualSigningProvisioningProfileType: 0' - line: '| VisionOSManualSigningProvisioningProfileType: 0' - line: '| appleEnableAutomaticSigning: 0' - line: '| iOSRequireARKit: 0' - line: '| iOSAutomaticallyDetectAndAddCapabilities: 1' - line: '| appleEnableProMotion: 0' - line: '| shaderPrecisionModel: 0' - line: '| clonedFromGUID: 00000000000000000000000000000000' - line: '| templatePackageId: ' - line: '| templateDefaultScene: ' - line: '| useCustomMainManifest: 0' - line: '| useCustomLauncherManifest: 0' - line: '| useCustomMainGradleTemplate: 0' - line: '| useCustomLauncherGradleManifest: 0' - line: '| useCustomBaseGradleTemplate: 0' - line: '| useCustomGradlePropertiesTemplate: 0' - line: '| useCustomGradleSettingsTemplate: 0' - line: '| useCustomProguardFile: 0' - line: '| AndroidTargetArchitectures: 2' - line: '| AndroidSplashScreenScale: 0' - line: '| androidSplashScreen: {instanceID: 0}' - line: '| AndroidKeystoreName: ' - line: '| AndroidKeyaliasName: ' - line: '| AndroidEnableArmv9SecurityFeatures: 0' - line: '| AndroidEnableArm64MTE: 0' - line: '| AndroidBuildApkPerCpuArchitecture: 0' - line: '| AndroidTVCompatibility: 1' - line: '| AndroidIsGame: 1' - line: '| AndroidEnableTango: 0' - line: '| androidEnableBanner: 1' - line: '| androidUseLowAccuracyLocation: 0' - line: '| androidUseCustomKeystore: 0' - line: '| m_AndroidBanners:' - line: '| - width: 320' - line: '| height: 180' - line: '| banner: {instanceID: 0}' - line: '| androidGamepadSupportLevel: 0' - line: '| AndroidMinifyRelease: 0' - line: '| AndroidMinifyDebug: 0' - line: '| AndroidValidateAppBundleSize: 1' - line: '| AndroidAppBundleSizeToValidate: 150' - line: '| AndroidReportGooglePlayAppDependencies: 1' - line: '| androidSymbolsSizeThreshold: 800' - line: '| m_BuildTargetIcons:' - line: '| - m_BuildTarget: ' - line: '| m_Icons:' - line: '| - serializedVersion: 2' - line: '| m_Icon: {instanceID: 0}' - line: '| m_Width: 128' - line: '| m_Height: 128' - line: '| m_Kind: 0' - line: '| m_BuildTargetPlatformIcons:' - line: '| - m_BuildTarget: Android' - line: '| m_Icons:' - line: '| - m_Textures: []' - line: '| m_Width: 432' - line: '| m_Height: 432' - line: '| m_Kind: 2' - line: '| m_SubKind: ' - line: '| - m_Textures: []' - line: '| m_Width: 324' - line: '| m_Height: 324' - line: '| m_Kind: 2' - line: '| m_SubKind: ' - line: '| - m_Textures: []' - line: '| m_Width: 216' - line: '| m_Height: 216' - line: '| m_Kind: 2' - line: '| m_SubKind: ' - line: '| - m_Textures: []' - line: '| m_Width: 162' - line: '| m_Height: 162' - line: '| m_Kind: 2' - line: '| m_SubKind: ' - line: '| - m_Textures: []' - line: '| m_Width: 108' - line: '| m_Height: 108' - line: '| m_Kind: 2' - line: '| m_SubKind: ' - line: '| - m_Textures: []' - line: '| m_Width: 81' - line: '| m_Height: 81' - line: '| m_Kind: 2' - line: '| m_SubKind: ' - line: '| - m_Textures: []' - line: '| m_Width: 192' - line: '| m_Height: 192' - line: '| m_Kind: 0' - line: '| m_SubKind: ' - line: '| - m_Textures: []' - line: '| m_Width: 144' - line: '| m_Height: 144' - line: '| m_Kind: 0' - line: '| m_SubKind: ' - line: '| - m_Textures: []' - line: '| m_Width: 96' - line: '| m_Height: 96' - line: '| m_Kind: 0' - line: '| m_SubKind: ' - line: '| - m_Textures: []' - line: '| m_Width: 72' - line: '| m_Height: 72' - line: '| m_Kind: 0' - line: '| m_SubKind: ' - line: '| - m_Textures: []' - line: '| m_Width: 48' - line: '| m_Height: 48' - line: '| m_Kind: 0' - line: '| m_SubKind: ' - line: '| - m_Textures: []' - line: '| m_Width: 36' - line: '| m_Height: 36' - line: '| m_Kind: 0' - line: '| m_SubKind: ' - line: '| - m_Textures: []' - line: '| m_Width: 192' - line: '| m_Height: 192' - line: '| m_Kind: 1' - line: '| m_SubKind: ' - line: '| - m_Textures: []' - line: '| m_Width: 144' - line: '| m_Height: 144' - line: '| m_Kind: 1' - line: '| m_SubKind: ' - line: '| - m_Textures: []' - line: '| m_Width: 96' - line: '| m_Height: 96' - line: '| m_Kind: 1' - line: '| m_SubKind: ' - line: '| - m_Textures: []' - line: '| m_Width: 72' - line: '| m_Height: 72' - line: '| m_Kind: 1' - line: '| m_SubKind: ' - line: '| - m_Textures: []' - line: '| m_Width: 48' - line: '| m_Height: 48' - line: '| m_Kind: 1' - line: '| m_SubKind: ' - line: '| - m_Textures: []' - line: '| m_Width: 36' - line: '| m_Height: 36' - line: '| m_Kind: 1' - line: '| m_SubKind: ' - line: '| - m_BuildTarget: iPhone' - line: '| m_Icons:' - line: '| - m_Textures: []' - line: '| m_Width: 180' - line: '| m_Height: 180' - line: '| m_Kind: 0' - line: '| m_SubKind: iPhone' - line: '| - m_Textures: []' - line: '| m_Width: 120' - line: '| m_Height: 120' - line: '| m_Kind: 0' - line: '| m_SubKind: iPhone' - line: '| - m_Textures: []' - line: '| m_Width: 167' - line: '| m_Height: 167' - line: '| m_Kind: 0' - line: '| m_SubKind: iPad' - line: '| - m_Textures: []' - line: '| m_Width: 152' - line: '| m_Height: 152' - line: '| m_Kind: 0' - line: '| m_SubKind: iPad' - line: '| - m_Textures: []' - line: '| m_Width: 76' - line: '| m_Height: 76' - line: '| m_Kind: 0' - line: '| m_SubKind: iPad' - line: '| - m_Textures: []' - line: '| m_Width: 120' - line: '| m_Height: 120' - line: '| m_Kind: 3' - line: '| m_SubKind: iPhone' - line: '| - m_Textures: []' - line: '| m_Width: 80' - line: '| m_Height: 80' - line: '| m_Kind: 3' - line: '| m_SubKind: iPhone' - line: '| - m_Textures: []' - line: '| m_Width: 80' - line: '| m_Height: 80' - line: '| m_Kind: 3' - line: '| m_SubKind: iPad' - line: '| - m_Textures: []' - line: '| m_Width: 40' - line: '| m_Height: 40' - line: '| m_Kind: 3' - line: '| m_SubKind: iPad' - line: '| - m_Textures: []' - line: '| m_Width: 87' - line: '| m_Height: 87' - line: '| m_Kind: 1' - line: '| m_SubKind: iPhone' - line: '| - m_Textures: []' - line: '| m_Width: 58' - line: '| m_Height: 58' - line: '| m_Kind: 1' - line: '| m_SubKind: iPhone' - line: '| - m_Textures: []' - line: '| m_Width: 29' - line: '| m_Height: 29' - line: '| m_Kind: 1' - line: '| m_SubKind: iPhone' - line: '| - m_Textures: []' - line: '| m_Width: 58' - line: '| m_Height: 58' - line: '| m_Kind: 1' - line: '| m_SubKind: iPad' - line: '| - m_Textures: []' - line: '| m_Width: 29' - line: '| m_Height: 29' - line: '| m_Kind: 1' - line: '| m_SubKind: iPad' - line: '| - m_Textures: []' - line: '| m_Width: 60' - line: '| m_Height: 60' - line: '| m_Kind: 2' - line: '| m_SubKind: iPhone' - line: '| - m_Textures: []' - line: '| m_Width: 40' - line: '| m_Height: 40' - line: '| m_Kind: 2' - line: '| m_SubKind: iPhone' - line: '| - m_Textures: []' - line: '| m_Width: 40' - line: '| m_Height: 40' - line: '| m_Kind: 2' - line: '| m_SubKind: iPad' - line: '| - m_Textures: []' - line: '| m_Width: 20' - line: '| m_Height: 20' - line: '| m_Kind: 2' - line: '| m_SubKind: iPad' - line: '| - m_Textures: []' - line: '| m_Width: 1024' - line: '| m_Height: 1024' - line: '| m_Kind: 4' - line: '| m_SubKind: App Store' - line: '| m_BuildTargetBatching: []' - line: '| m_BuildTargetShaderSettings: []' - line: '| m_BuildTargetGraphicsJobs:' - line: '| - m_BuildTarget: MacStandaloneSupport' - line: '| m_GraphicsJobs: 0' - line: '| - m_BuildTarget: Switch' - line: '| m_GraphicsJobs: 0' - line: '| - m_BuildTarget: MetroSupport' - line: '| m_GraphicsJobs: 0' - line: '| - m_BuildTarget: AppleTVSupport' - line: '| m_GraphicsJobs: 0' - line: '| - m_BuildTarget: BJMSupport' - line: '| m_GraphicsJobs: 0' - line: '| - m_BuildTarget: LinuxStandaloneSupport' - line: '| m_GraphicsJobs: 0' - line: '| - m_BuildTarget: PS4Player' - line: '| m_GraphicsJobs: 0' - line: '| - m_BuildTarget: iOSSupport' - line: '| m_GraphicsJobs: 0' - line: '| - m_BuildTarget: WindowsStandaloneSupport' - line: '| m_GraphicsJobs: 0' - line: '| - m_BuildTarget: XboxOnePlayer' - line: '| m_GraphicsJobs: 0' - line: '| - m_BuildTarget: LuminSupport' - line: '| m_GraphicsJobs: 0' - line: '| - m_BuildTarget: CloudRendering' - line: '| m_GraphicsJobs: 0' - line: '| - m_BuildTarget: AndroidPlayer' - line: '| m_GraphicsJobs: 0' - line: '| - m_BuildTarget: WebGLSupport' - line: '| m_GraphicsJobs: 0' - line: '| m_BuildTargetGraphicsJobMode:' - line: '| - m_BuildTarget: PS4Player' - line: '| m_GraphicsJobMode: 0' - line: '| - m_BuildTarget: XboxOnePlayer' - line: '| m_GraphicsJobMode: 0' - line: '| m_BuildTargetGraphicsAPIs:' - line: '| - m_BuildTarget: AndroidPlayer' - line: '| m_APIs: 0b000000' - line: '| m_Automatic: 0' - line: '| - m_BuildTarget: iOSSupport' - line: '| m_APIs: 10000000' - line: '| m_Automatic: 1' - line: '| m_BuildTargetVRSettings:' - line: '| - m_BuildTarget: Android' - line: '| m_Enabled: 0' - line: '| m_Devices:' - line: '| - Oculus' - line: '| - m_BuildTarget: Windows Store Apps' - line: '| m_Enabled: 0' - line: '| m_Devices: []' - line: '| - m_BuildTarget: N3DS' - line: '| m_Enabled: 0' - line: '| m_Devices: []' - line: '| - m_BuildTarget: PS3' - line: '| m_Enabled: 0' - line: '| m_Devices: []' - line: '| - m_BuildTarget: PS4' - line: '| m_Enabled: 0' - line: '| m_Devices:' - line: '| - PlayStationVR' - line: '| - m_BuildTarget: PSM' - line: '| m_Enabled: 0' - line: '| m_Devices: []' - line: '| - m_BuildTarget: PSP2' - line: '| m_Enabled: 0' - line: '| m_Devices: []' - line: '| - m_BuildTarget: SamsungTV' - line: '| m_Enabled: 0' - line: '| m_Devices: []' - line: '| - m_BuildTarget: Standalone' - line: '| m_Enabled: 0' - line: '| m_Devices:' - line: '| - Oculus' - line: '| - m_BuildTarget: Tizen' - line: '| m_Enabled: 0' - line: '| m_Devices: []' - line: '| - m_BuildTarget: WebGL' - line: '| m_Enabled: 0' - line: '| m_Devices: []' - line: '| - m_BuildTarget: WebPlayer' - line: '| m_Enabled: 0' - line: '| m_Devices: []' - line: '| - m_BuildTarget: WiiU' - line: '| m_Enabled: 0' - line: '| m_Devices: []' - line: '| - m_BuildTarget: Xbox360' - line: '| m_Enabled: 0' - line: '| m_Devices: []' - line: '| - m_BuildTarget: XboxOne' - line: '| m_Enabled: 0' - line: '| m_Devices: []' - line: '| - m_BuildTarget: iPhone' - line: '| m_Enabled: 0' - line: '| m_Devices: []' - line: '| - m_BuildTarget: tvOS' - line: '| m_Enabled: 0' - line: '| m_Devices: []' - line: '| m_DefaultShaderChunkSizeInMB: 16' - line: '| m_DefaultShaderChunkCount: 0' - line: '| openGLRequireES31: 1' - line: '| openGLRequireES31AEP: 0' - line: '| openGLRequireES32: 0' - line: '| m_TemplateCustomTags: {}' - line: '| mobileMTRendering:' - line: '| Android: 1' - line: '| iPhone: 1' - line: '| tvOS: 1' - line: '| m_BuildTargetGroupLightmapEncodingQuality:' - line: '| - serializedVersion: 2' - line: '| m_BuildTarget: Standalone' - line: '| m_EncodingQuality: 1' - line: '| - serializedVersion: 2' - line: '| m_BuildTarget: XboxOne' - line: '| m_EncodingQuality: 1' - line: '| - serializedVersion: 2' - line: '| m_BuildTarget: PS4' - line: '| m_EncodingQuality: 1' - line: '| m_BuildTargetGroupLightmapSettings: []' - line: '| m_BuildTargetGroupLoadStoreDebugModeSettings: []' - line: '| m_BuildTargetNormalMapEncoding: []' - line: '| m_BuildTargetDefaultTextureCompressionFormat: []' - line: '| playModeTestRunnerEnabled: 0' - line: '| runPlayModeTestAsEditModeTest: 0' - line: '| actionOnDotNetUnhandledException: 1' - line: '| editorGfxJobOverride: 1' - line: '| enableInternalProfiler: 0' - line: '| logObjCUncaughtExceptions: 1' - line: '| enableCrashReportAPI: 0' - line: '| cameraUsageDescription: ' - line: '| locationUsageDescription: ' - line: '| microphoneUsageDescription: ' - line: '| bluetoothUsageDescription: ' - line: '| macOSTargetOSVersion: 11.0' - line: '| switchNMETAOverride: ' - line: '| switchNetLibKey: ' - line: '| switchSocketMemoryPoolSize: 6144' - line: '| switchSocketAllocatorPoolSize: 128' - line: '| switchSocketConcurrencyLimit: 14' - line: '| switchScreenResolutionBehavior: 2' - line: '| switchUseCPUProfiler: 0' - line: '| switchEnableFileSystemTrace: 0' - line: '| switchLTOSetting: 0' - line: '| switchApplicationID: 0x01004b9000490000' - line: '| switchNSODependencies: ' - line: '| switchCompilerFlags: ' - line: '| switchTitleNames_0: ' - line: '| switchTitleNames_1: ' - line: '| switchTitleNames_2: ' - line: '| switchTitleNames_3: ' - line: '| switchTitleNames_4: ' - line: '| switchTitleNames_5: ' - line: '| switchTitleNames_6: ' - line: '| switchTitleNames_7: ' - line: '| switchTitleNames_8: ' - line: '| switchTitleNames_9: ' - line: '| switchTitleNames_10: ' - line: '| switchTitleNames_11: ' - line: '| switchTitleNames_12: ' - line: '| switchTitleNames_13: ' - line: '| switchTitleNames_14: ' - line: '| switchTitleNames_15: ' - line: '| switchPublisherNames_0: ' - line: '| switchPublisherNames_1: ' - line: '| switchPublisherNames_2: ' - line: '| switchPublisherNames_3: ' - line: '| switchPublisherNames_4: ' - line: '| switchPublisherNames_5: ' - line: '| switchPublisherNames_6: ' - line: '| switchPublisherNames_7: ' - line: '| switchPublisherNames_8: ' - line: '| switchPublisherNames_9: ' - line: '| switchPublisherNames_10: ' - line: '| switchPublisherNames_11: ' - line: '| switchPublisherNames_12: ' - line: '| switchPublisherNames_13: ' - line: '| switchPublisherNames_14: ' - line: '| switchPublisherNames_15: ' - line: '| switchIcons_0: {instanceID: 0}' - line: '| switchIcons_1: {instanceID: 0}' - line: '| switchIcons_2: {instanceID: 0}' - line: '| switchIcons_3: {instanceID: 0}' - line: '| switchIcons_4: {instanceID: 0}' - line: '| switchIcons_5: {instanceID: 0}' - line: '| switchIcons_6: {instanceID: 0}' - line: '| switchIcons_7: {instanceID: 0}' - line: '| switchIcons_8: {instanceID: 0}' - line: '| switchIcons_9: {instanceID: 0}' - line: '| switchIcons_10: {instanceID: 0}' - line: '| switchIcons_11: {instanceID: 0}' - line: '| switchIcons_12: {instanceID: 0}' - line: '| switchIcons_13: {instanceID: 0}' - line: '| switchIcons_14: {instanceID: 0}' - line: '| switchIcons_15: {instanceID: 0}' - line: '| switchSmallIcons_0: {instanceID: 0}' - line: '| switchSmallIcons_1: {instanceID: 0}' - line: '| switchSmallIcons_2: {instanceID: 0}' - line: '| switchSmallIcons_3: {instanceID: 0}' - line: '| switchSmallIcons_4: {instanceID: 0}' - line: '| switchSmallIcons_5: {instanceID: 0}' - line: '| switchSmallIcons_6: {instanceID: 0}' - line: '| switchSmallIcons_7: {instanceID: 0}' - line: '| switchSmallIcons_8: {instanceID: 0}' - line: '| switchSmallIcons_9: {instanceID: 0}' - line: '| switchSmallIcons_10: {instanceID: 0}' - line: '| switchSmallIcons_11: {instanceID: 0}' - line: '| switchSmallIcons_12: {instanceID: 0}' - line: '| switchSmallIcons_13: {instanceID: 0}' - line: '| switchSmallIcons_14: {instanceID: 0}' - line: '| switchSmallIcons_15: {instanceID: 0}' - line: '| switchManualHTML: ' - line: '| switchAccessibleURLs: ' - line: '| switchLegalInformation: ' - line: '| switchMainThreadStackSize: 1048576' - line: '| switchPresenceGroupId: 0x01004b9000490000' - line: '| switchLogoHandling: 0' - line: '| switchReleaseVersion: 0' - line: '| switchDisplayVersion: 1.0.0' - line: '| switchStartupUserAccount: 0' - line: '| switchSupportedLanguagesMask: 0' - line: '| switchLogoType: 0' - line: '| switchApplicationErrorCodeCategory: ' - line: '| switchUserAccountSaveDataSize: 0' - line: '| switchUserAccountSaveDataJournalSize: 0' - line: '| switchApplicationAttribute: 0' - line: '| switchCardSpecSize: 4' - line: '| switchCardSpecClock: 25' - line: '| switchRatingsMask: 0' - line: '| switchRatingsInt_0: 0' - line: '| switchRatingsInt_1: 0' - line: '| switchRatingsInt_2: 0' - line: '| switchRatingsInt_3: 0' - line: '| switchRatingsInt_4: 0' - line: '| switchRatingsInt_5: 0' - line: '| switchRatingsInt_6: 0' - line: '| switchRatingsInt_7: 0' - line: '| switchRatingsInt_8: 0' - line: '| switchRatingsInt_9: 0' - line: '| switchRatingsInt_10: 0' - line: '| switchRatingsInt_11: 0' - line: '| switchRatingsInt_12: 0' - line: '| switchLocalCommunicationIds_0: 0x01004b9000490000' - line: '| switchLocalCommunicationIds_1: ' - line: '| switchLocalCommunicationIds_2: ' - line: '| switchLocalCommunicationIds_3: ' - line: '| switchLocalCommunicationIds_4: ' - line: '| switchLocalCommunicationIds_5: ' - line: '| switchLocalCommunicationIds_6: ' - line: '| switchLocalCommunicationIds_7: ' - line: '| switchParentalControl: 0' - line: '| switchAllowsScreenshot: 1' - line: '| switchAllowsVideoCapturing: 1' - line: '| switchAllowsRuntimeAddOnContentInstall: 0' - line: '| switchDataLossConfirmation: 0' - line: '| switchUserAccountLockEnabled: 0' - line: '| switchSystemResourceMemory: 16777216' - line: '| switchSupportedNpadStyles: 3' - line: '| switchNativeFsCacheSize: 32' - line: '| switchIsHoldTypeHorizontal: 0' - line: '| switchSupportedNpadCount: 8' - line: '| switchEnableTouchScreen: 1' - line: '| switchSocketConfigEnabled: 0' - line: '| switchTcpInitialSendBufferSize: 32' - line: '| switchTcpInitialReceiveBufferSize: 64' - line: '| switchTcpAutoSendBufferSizeMax: 256' - line: '| switchTcpAutoReceiveBufferSizeMax: 256' - line: '| switchUdpSendBufferSize: 9' - line: '| switchUdpReceiveBufferSize: 42' - line: '| switchSocketBufferEfficiency: 4' - line: '| switchSocketInitializeEnabled: 1' - line: '| switchNetworkInterfaceManagerInitializeEnabled: 1' - line: '| switchDisableHTCSPlayerConnection: 0' - line: '| switchUseNewStyleFilepaths: 0' - line: '| switchUseLegacyFmodPriorities: 1' - line: '| switchUseMicroSleepForYield: 1' - line: '| switchEnableRamDiskSupport: 0' - line: '| switchMicroSleepForYieldTime: 25' - line: '| switchRamDiskSpaceSize: 12' - line: '| switchUpgradedPlayerSettingsToNMETA: 0' - line: '| ps4NPAgeRating: 12' - line: '| ps4NPTitleSecret: ' - line: '| ps4NPTrophyPackPath: ' - line: '| ps4ParentalLevel: 1' - line: '| ps4ContentID: ED1633-NPXX51362_00-0000000000000000' - line: '| ps4Category: 0' - line: '| ps4MasterVersion: 01.00' - line: '| ps4AppVersion: 01.00' - line: '| ps4AppType: 0' - line: '| ps4ParamSfxPath: ' - line: '| ps4VideoOutPixelFormat: 0' - line: '| ps4VideoOutInitialWidth: 1920' - line: '| ps4VideoOutBaseModeInitialWidth: 1920' - line: '| ps4VideoOutReprojectionRate: 120' - line: '| ps4PronunciationXMLPath: ' - line: '| ps4PronunciationSIGPath: ' - line: '| ps4BackgroundImagePath: ' - line: '| ps4StartupImagePath: ' - line: '| ps4StartupImagesFolder: ' - line: '| ps4IconImagesFolder: ' - line: '| ps4SaveDataImagePath: ' - line: '| ps4SdkOverride: ' - line: '| ps4BGMPath: ' - line: '| ps4ShareFilePath: ' - line: '| ps4ShareOverlayImagePath: ' - line: '| ps4PrivacyGuardImagePath: ' - line: '| ps4ExtraSceSysFile: ' - line: '| ps4NPtitleDatPath: ' - line: '| ps4RemotePlayKeyAssignment: -1' - line: '| ps4RemotePlayKeyMappingDir: ' - line: '| ps4PlayTogetherPlayerCount: 0' - line: '| ps4EnterButtonAssignment: 1' - line: '| ps4ApplicationParam1: 0' - line: '| ps4ApplicationParam2: 0' - line: '| ps4ApplicationParam3: 0' - line: '| ps4ApplicationParam4: 0' - line: '| ps4DownloadDataSize: 0' - line: '| ps4GarlicHeapSize: 2048' - line: '| ps4ProGarlicHeapSize: 2560' - line: '| playerPrefsMaxSize: 32768' - line: '| ps4Passcode: eaoEiIgxIX4a2dREbbSqWy6yhKIDCdJO' - line: '| ps4pnSessions: 1' - line: '| ps4pnPresence: 1' - line: '| ps4pnFriends: 1' - line: '| ps4pnGameCustomData: 1' - line: '| playerPrefsSupport: 0' - line: '| enableApplicationExit: 0' - line: '| resetTempFolder: 1' - line: '| restrictedAudioUsageRights: 0' - line: '| ps4UseResolutionFallback: 0' - line: '| ps4ReprojectionSupport: 0' - line: '| ps4UseAudio3dBackend: 0' - line: '| ps4UseLowGarlicFragmentationMode: 1' - line: '| ps4SocialScreenEnabled: 0' - line: '| ps4ScriptOptimizationLevel: 3' - line: '| ps4Audio3dVirtualSpeakerCount: 14' - line: '| ps4attribCpuUsage: 0' - line: '| ps4PatchPkgPath: ' - line: '| ps4PatchLatestPkgPath: ' - line: '| ps4PatchChangeinfoPath: ' - line: '| ps4PatchDayOne: 0' - line: '| ps4attribUserManagement: 0' - line: '| ps4attribMoveSupport: 0' - line: '| ps4attrib3DSupport: 0' - line: '| ps4attribShareSupport: 0' - line: '| ps4attribExclusiveVR: 0' - line: '| ps4disableAutoHideSplash: 0' - line: '| ps4videoRecordingFeaturesUsed: 0' - line: '| ps4contentSearchFeaturesUsed: 0' - line: '| ps4CompatibilityPS5: 0' - line: '| ps4AllowPS5Detection: 0' - line: '| ps4GPU800MHz: 1' - line: '| ps4attribEyeToEyeDistanceSettingVR: 0' - line: '| ps4IncludedModules: []' - line: '| ps4attribVROutputEnabled: 0' - line: '| monoEnv: ' - line: '| splashScreenBackgroundSourceLandscape: {instanceID: 0}' - line: '| splashScreenBackgroundSourcePortrait: {instanceID: 0}' - line: '| blurSplashScreenBackground: 1' - line: '| spritePackerPolicy: ' - line: '| webGLMemorySize: 256' - line: '| webGLExceptionSupport: 1' - line: '| webGLNameFilesAsHashes: 0' - line: '| webGLShowDiagnostics: 0' - line: '| webGLDataCaching: 0' - line: '| webGLDebugSymbols: 0' - line: '| webGLEmscriptenArgs: ' - line: '| webGLModulesDirectory: ' - line: '| webGLTemplate: APPLICATION:Default' - line: '| webGLAnalyzeBuildSize: 0' - line: '| webGLUseEmbeddedResources: 0' - line: '| webGLCompressionFormat: 1' - line: '| webGLWasmArithmeticExceptions: 0' - line: '| webGLLinkerTarget: 1' - line: '| webGLThreadsSupport: 0' - line: '| webGLDecompressionFallback: 0' - line: '| webGLInitialMemorySize: 32' - line: '| webGLMaximumMemorySize: 2048' - line: '| webGLMemoryGrowthMode: 2' - line: '| webGLMemoryLinearGrowthStep: 16' - line: '| webGLMemoryGeometricGrowthStep: 0.2' - line: '| webGLMemoryGeometricGrowthCap: 96' - line: '| webGLEnableWebGPU: 0' - line: '| webGLPowerPreference: 2' - line: '| webGLWebAssemblyTable: 0' - line: '| webGLWebAssemblyBigInt: 0' - line: '| webGLCloseOnQuit: 0' - line: '| webWasm2023: 0' - line: '| scriptingDefineSymbols:' - line: '| Android: UNITY;ENABLE_VIEW' - line: '| Server: UNITY;ENABLE_VIEW' - line: '| Standalone: UNITY;ENABLE_VIEW' - line: '| WebGL: UNITY;ENABLE_VIEW' - line: '| iPhone: UNITY;ENABLE_VIEW' - line: '| additionalCompilerArguments: {}' - line: '| platformArchitecture:' - line: '| iPhone: 1' - line: '| scriptingBackend:' - line: '| Android: 1' - line: '| Server: 0' - line: '| Standalone: 1' - line: '| iPhone: 1' - line: '| il2cppCompilerConfiguration: {}' - line: '| il2cppCodeGeneration: {}' - line: '| il2cppStacktraceInformation: {}' - line: '| managedStrippingLevel:' - line: '| Android: 1' - line: '| EmbeddedLinux: 1' - line: '| GameCoreScarlett: 1' - line: '| GameCoreXboxOne: 1' - line: '| Lumin: 1' - line: '| Nintendo Switch: 1' - line: '| PS4: 1' - line: '| PS5: 1' - line: '| Stadia: 1' - line: '| Standalone: 1' - line: '| WebGL: 1' - line: '| Windows Store Apps: 1' - line: '| XboxOne: 1' - line: '| iPhone: 1' - line: '| tvOS: 1' - line: '| incrementalIl2cppBuild: {}' - line: '| suppressCommonWarnings: 1' - line: '| allowUnsafeCode: 1' - line: '| useDeterministicCompilation: 1' - line: '| additionalIl2CppArgs: ' - line: '| scriptingRuntimeVersion: 1' - line: '| gcIncremental: 0' - line: '| gcWBarrierValidation: 0' - line: '| apiCompatibilityLevelPerPlatform:' - line: '| Android: 3' - line: '| Server: 3' - line: '| Standalone: 3' - line: '| editorAssembliesCompatibilityLevel: 2' - line: '| m_RenderingPath: 1' - line: '| m_MobileRenderingPath: 1' - line: '| metroPackageName: Unity' - line: '| metroPackageVersion: ' - line: '| metroCertificatePath: ' - line: '| metroCertificatePassword: ' - line: '| metroCertificateSubject: ' - line: '| metroCertificateIssuer: ' - line: '| metroCertificateNotAfter: 0000000000000000' - line: '| metroApplicationDescription: Unity' - line: '| wsaImages: {}' - line: '| metroTileShortName: ' - line: '| metroTileShowName: 0' - line: '| metroMediumTileShowName: 0' - line: '| metroLargeTileShowName: 0' - line: '| metroWideTileShowName: 0' - line: '| metroSupportStreamingInstall: 0' - line: '| metroLastRequiredScene: 0' - line: '| metroDefaultTileSize: 1' - line: '| metroTileForegroundText: 1' - line: '| metroTileBackgroundColor: {r: 0, g: 0, b: 0, a: 1}' - line: '| metroSplashScreenBackgroundColor: {r: 0, g: 0, b: 0, a: 1}' - line: '| metroSplashScreenUseBackgroundColor: 0' - line: '| syncCapabilities: 0' - line: '| platformCapabilities: {}' - line: '| metroTargetDeviceFamilies: {}' - line: '| metroFTAName: ' - line: '| metroFTAFileTypes: []' - line: '| metroProtocolName: ' - line: '| vcxProjDefaultLanguage: ' - line: '| XboxOneProductId: ' - line: '| XboxOneUpdateKey: ' - line: '| XboxOneSandboxId: ' - line: '| XboxOneContentId: ' - line: '| XboxOneTitleId: ' - line: '| XboxOneSCId: ' - line: '| XboxOneGameOsOverridePath: ' - line: '| XboxOnePackagingOverridePath: ' - line: '| XboxOneAppManifestOverridePath: ' - line: '| XboxOneVersion: 1.0.0.0' - line: '| XboxOnePackageEncryption: 0' - line: '| XboxOnePackageUpdateGranularity: 2' - line: '| XboxOneDescription: ' - line: '| XboxOneLanguage:' - line: '| - enus' - line: '| XboxOneCapability: []' - line: '| XboxOneGameRating: {}' - line: '| XboxOneIsContentPackage: 0' - line: '| XboxOneEnhancedXboxCompatibilityMode: 0' - line: '| XboxOneEnableGPUVariability: 0' - line: '| XboxOneSockets: {}' - line: '| XboxOneSplashScreen: {instanceID: 0}' - line: '| XboxOneAllowedProductIds: []' - line: '| XboxOnePersistentLocalStorageSize: 0' - line: '| XboxOneXTitleMemory: 8' - line: '| XboxOneOverrideIdentityName: ' - line: '| XboxOneOverrideIdentityPublisher: ' - line: '| vrEditorSettings: {}' - line: '| cloudServicesEnabled:' - line: '| Analytics: 0' - line: '| Build: 0' - line: '| Collab: 0' - line: '| ErrorHub: 0' - line: '| Game_Performance: 0' - line: '| Hub: 0' - line: '| Purchasing: 0' - line: '| UNet: 0' - line: '| Unity_Ads: 0' - line: '| luminIcon:' - line: '| m_Name: ' - line: '| m_ModelFolderPath: ' - line: '| m_PortalFolderPath: ' - line: '| luminCert:' - line: '| m_CertPath: ' - line: '| m_SignPackage: 1' - line: '| luminIsChannelApp: 0' - line: '| luminVersion:' - line: '| m_VersionCode: 1' - line: '| m_VersionName: ' - line: '| hmiPlayerDataPath: ' - line: '| hmiForceSRGBBlit: 1' - line: '| embeddedLinuxEnableGamepadInput: 1' - line: '| hmiCpuConfiguration: ' - line: '| hmiLogStartupTiming: 0' - line: '| qnxGraphicConfPath: ' - line: '| apiCompatibilityLevel: 3' - line: '| captureStartupLogs: {}' - line: '| activeInputHandler: 0' - line: '| windowsGamepadBackendHint: 0' - line: '| cloudProjectId: ' - line: '| framebufferDepthMemorylessMode: 0' - line: '| qualitySettingsNames: []' - line: '| projectName: ' - line: '| organizationId: ' - line: '| cloudEnabled: 0' - line: '| legacyClampBlendShapeWeights: 0' - line: '| hmiLoadingImage: {instanceID: 0}' - line: '| platformRequiresReadableAssets: 0' - line: '| virtualTexturingSupportEnabled: 0' - line: '| insecureHttpOption: 0' - line: '| androidVulkanDenyFilterList: []' - line: '| androidVulkanAllowFilterList: []' - line: '| ' references: version: 2 RefIds: - rid: 6418588523325555037 type: {class: OSXStandaloneBuildProfile, ns: UnityEditor.OSXStandalone, asm: UnityEditor.OSXStandalone.Extensions} data: m_Development: 0 m_ConnectProfiler: 0 m_BuildWithDeepProfilingSupport: 0 m_AllowDebugging: 0 m_WaitForManagedDebugger: 0 m_ManagedDebuggerFixedPort: 0 m_ExplicitNullChecks: 0 m_ExplicitDivideByZeroChecks: 0 m_ExplicitArrayBoundsChecks: 0 m_CompressionType: 0 m_InstallInBuildFolder: 0 m_MacOSXcodeBuildConfig: 1 m_Architecture: 2 m_CreateXcodeProject: 0 ================================================ FILE: Assets/Settings/Build Profiles/Mac.asset.meta ================================================ fileFormatVersion: 2 guid: b6ae1720552ce4174bde3c7ee1ff65aa NativeFormatImporter: externalObjects: {} mainObjectFileID: 11400000 userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Settings/Build Profiles.meta ================================================ fileFormatVersion: 2 guid: 1c7616f0661a042bab7d5e2ed3c6aac1 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/Settings.meta ================================================ fileFormatVersion: 2 guid: a9ccb235ecf71412cb659c2bd837eba0 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Assets/link.xml ================================================ ================================================ FILE: Assets/link.xml.meta ================================================ fileFormatVersion: 2 guid: 4c3a563f505464efb8376db1bae84009 TextScriptImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Book/1.1运行指南.md ================================================ # 运行步骤 1. IDE安装 **使用[Rider2024.3](https://www.jetbrains.com/zh-cn/rider/)**(更新到最新版),需要安装以下内容: - windows上用visual studio安装最新的.Net8, mac请用homebrew安装.Net8跟powershell - 不支持VS,新人用VS搞出各种问题请不要来问我,我也没用过VS,后期搞熟了可以自己改用VS 2. 出现如下报错 [Package Manager Window] Error searching for packages. 不用处理!!!!!! 这是因为github package的注册表跟unity有些不兼容导致的,忽略即可 3. 该分支必须使用Unity**6000.0.25**(初学者请在此版本用熟后再切换其他版本) 4. 整个过程请开启全局翻墙,否则各种unity包 nuget包下载不下来,报memerypack等错误 5. 注意一定要clone一个新的工程!clone一个新的工程!clone一个新的工程!重要的事情说三遍!!! 6. 启动UnityHub,打开(Open) -> 选中'ET'文件夹所在目录后打开工程,特别注意,ET9的目录结构跟ET8.1完全不同,请全新下载整个工程 7. 打开工程后,点击Unity菜单 -> Edit -> Preferences -> External Tools,点击下拉框'External ScriptEditor'选择Rider,Generate .csproj files for要勾选前两个 8. 在你的github中获取token,获取方法:打开 https://github.com/settings/tokens 选择tokens(classic),点击generate new token,下面全部勾选,点击确定,复制你的token保存 9. 打开菜单ET->Init->Manage scope registries, 点击ET-Packages Edit, 把你的github token粘贴到token里面 save,然后再看到User Credentials on this computer点+号, Registry URL填入 https://npm.pkg.github.com/@ET-Packages token填入你的github token always auth 填 true 点击add 10. 在https://github.com/orgs/ET-Packages/packages 中选择一个demo包安装,目前有两个demo, cn.etetet.lockstep跟cn.etetet.statesync分别是帧同步跟状态同步(一个工程只能安装一个demo,不能同时装多个) 新手最好先选择状态同步,因为我都是先修改状态同步,偶尔会漏改了帧同步,等状态同步跑通再尝试帧同步 11. 两种安装包的方式,第一种,可以关闭Unity,打开Packages/manifest.json, 在dependencies中加上demo包跟版本号,例如"cn.etetet.lockstep": "0.0.36", 注意后面有个逗号,重启Unity,Unity就会自动下载依赖,并且会显示下载进度,该方案安装速度更快。第二种,打开Unity的菜单Windows->PackageManager,点击左上角加号,选择add package by name,输入包名,点击右边add,会把所有的依赖包下载。这种因为Unity有bug导致安装很慢。推荐使用第一种安装方式。 12. 注意,安装完成后,在PackageManager中观察一下,所有的ET开头的包都会是Custom包,这是因为安装后ET会把包挪到Packages目录,从而变成Custom包,如果有ET开头的包不是Custom,请重新点击ET->Init->RepairDependencies然后刷新Unity 13. 运行Unity菜单 ET->StateSync->Init或者ET->LockStep->Init (这一步会导Excel 导Proto 生成assemlbyreference 添加INITED宏,并且自动链接demo中的ET.sln到根目录,并且把Loader包中的GlobalConfig中的SceneName设置成demo名) 14. Unity中将Packages/ET.Loader/Resources/GlobalConfig中的CodeMode改成ClientServer,将SceneName设置成demo名StateSync或者LockStep(这个在上一步会自动设置,你这里检查一下名字是不是demo名), 这个选项是将服务端运行在Unity中,一体化运行。如果客户端单独打包的话,这里要使用Client 15. Packages/ET.YooAssets/Resources/YooConfig中将EPlayMode修改为EditorSimulateMode(具体请看YooAssets的官方文档) 16. 点击Unity菜单 Assets->Open C# Project,这里由于修改了rider插件,会自动打开ET.sln 17. 编译整个ET.sln, 注意要翻墙,否则可能nuget包下载不下来,导致编译出错(翻墙后如果还有报错解决不了可以尝试先用VS打开ET.sln编译一次后再回到Rider重新编译一次) 18. Unity中, 然后双击Packages/ET.Loader/Scenes/Init场景,点击Play(▶)即可运行 19. 帧同步默认是一个人匹配,如果需要多人匹配,修改**LSConstValue.cs**中的**MatchCount** 客户端服务端都要重新编译,都要重启即可 20. 注意要独立启动服务器,右键UnityHub,以管理员身份运行UnityHub,然后启动Unity(没有管理员启动是不行的,因为服务端要开启http服务,普通权限开不了), 停止Unity Play,点开Unity菜单->ET->Server Tools->Start Server(Single Process),这样就单独启动了服务端。打开Unity菜单 -> ET -> BuildTool中CodeMode改成Client,点击Unity Play,登录。 如果还是连接不上报10037错误,注意看ET/Logs目录,看有没有Error日志。 如果要用rider启动服务器,rider也必须用管理员权限启动 注意一定要用 netsh http delete urlacl 命令删除掉所有自己添加的urlacl,具体使用方法请谷歌 客户端注意要打开cn.etetet.loader/Resources/GlobalConfig, 把CodeMode换成Client 21. 注意独立运行服务器的目录不再是Bin目录,而是Bin的上一层目录,也就是Unity目录,比如 dotnet.exe Bin/ET.App.dll --Console=1。用rider启动默认是在Bin目录,需要自己修改运行目录,去掉Bin 22. 有问题请论坛提问,贴出服务端error log跟客户端error log,没有日志无法回复 # 打包过程 1. 去掉Platform Settings里的Development Build 2. 点击HybridCLR -> Installer,点击安装,等待安装完成 3. 右键编译ET.sln 4. 点击HybridCLR -> Generate -> All 5. 点击ET -> HybridCLR -> CopyAotDlls,这一步会把需要补充元数据的dll复制到'Assets/Bundles/AotDlls'目录 6. 打开YooAsset -> AssetBundle Builder窗口,按照以下步骤操作: ①BuildPipeline : '**ScriptableBuildPipeline**' ②BuildMode : '**IncrementalBuild**' ③CopyBuildinFileOption : '**ClearAndCopyAll**' ④点击'**Click Build**' 7. ET.YooAssets/Resources YooConfig中 EPlayMode选择'HostPlayMode', 打开Unity菜单 -> ET -> BuildTool,点击'BuildPackage',Windows下生成的exe在'ET/Release'里面 另:**请自行研究**YooAsset包管理库的使用方式([YooAsset官网](https://www.yooasset.com/)) # 热重载 1. 若需要体验此功能,需要在Unity菜单 -> Edit -> Preferences -> General窗口的 'ScriptChangesWhilePlaying' 中 选择 '**RecompileAfterFinishedPlaying**' 2. 运行后修改并编译代码,点击Unity菜单 -> ET -> Reload(或按快捷键**F7**)即可 # 注意事项: 一、常见出错原因: 1. 中文目录 2. Rider或VS没有更新到最新版本 3. Rider或VS没有安装相关组件 4. 没安装.Net8 5. 没编译服务端所有工程 6. Unity版本不兼容 7. Win7用户,没有特别设置 8. 如果打包报错缺少"StreamingAsset",自己在 'ET/Unity/Assets' 下新建一个 StreamingAsset 文件夹即可 ================================================ FILE: Book/1.2Why use .net core.md ================================================ # Why use C# .net core for server-side? Game server side from the early single service to distributed, the development is more and more complex, the stability, development efficiency requirements are more and more high. The choice of development language has also gradually changed, from C to C++ to C++ + PYTHON or C++ + LUA, and now many companies are using erlang, go, java, c#. At present, it is a blossoming situation. But if you were to redo an online game server, without considering the compatibility with the company or what is already there, how would you choose? I thought carefully about this issue, there are probably several aspects of this need to consider: 1. ###### 1. Stability of the language (fatal) The game server is characterized by high load and low latency. So generally the server-side process is with state, once hung means data loss, this is intolerable. ###### 2. Runtime hot more (fatal) The game server logic is extremely complex and prone to bugs, but it can't be stopped often, so hot shifts to fix bugs are necessary. If a bug occurs, the developer can write code immediately and then fix it with a hot-change, which is not felt by online users at all. ###### 3. Availability of concurrent process support (5 stars importance) With distributed server architecture, there is bound to be a lot of interaction between processes. Since it is difficult to split the game logic into multiple threads, it is generally single-threaded logic. If there is no concurrent support, a large number of callbacks are bound to be generated and code maintenance will become very difficult. ###### 4. Compilation speed (5 stars in importance) In c++ development, 30% of the time is wasted on compilation. If compilation is fast or not needed, it will greatly improve development efficiency. ###### 5. Cross-platform (4 stars) Generally the game server is set up on linux. But the usual development, the use of windows will be more convenient, if cross-platform, development and testing efficiency will be greatly improved, and do not need to get a separate development machine, the local computer can meet the usual development ###### 6. Readability, refactorability (3 stars) The code can be reconstructed to greatly reduce the difficulty of writing code ###### 7. Whether the library is complete, whether the ecology is perfect (3 stars) Library is complete, ecological good, you need to build their own wheel is less ###### 8. Unified language with the client (3 stars) Client-server shared language, the advantages are very obvious, many codes can be reused, logic programmers no longer need to distinguish between the front and back end, both ends can write, a person can complete a function, greatly reducing the time cost of communication. ###### 9. IDE support (3 stars) Code hints, refactoring and other support, excellent IDE can improve the development efficiency by several times. ###### 10. Language performance (1 star) Currently server performance is not too much of a problem, but good performance is better than poor performance. | Languages | C# | C/C++ | Java | Go | Lua | Python | Erlang | | -- | :--: | :--: | :--: | :--: | :--: | :--: | :--: | | Stability | Stable | Easy to hang | Stable | Stable | Stable | Stable | Stable | Stable | Stable | Runtime Hot Update | Support | Difficult to support | Support | Doesn't support | Support | Support | Support | Cross-Platform | Support | Difficult to Support | Support | Difficult to Support | Support | Support | Support | Coroutine | Have | Need to Implement | Bad Support | Support | Support | Support | Support | Compilation speed | fast | slow | fast | fast | no compilation | no compilation | fast | Readability | Good | Fair | Good | Fair | Poor | Poor | Poor| | Game Library and Ecology | Good | Good | Average | Average | Poor | Good | Average| | Client Unified Language | Unity | Unity, UE4 | Not available | Not available | Unity, UE4 | UE4 | Not available | IDE Support | Good | Good | Good | Average | Poor | Poor | Poor| | Language performance | Good | Very good | Good | Good | Poor | Very poor | Poor | As you can see from the table. 1: 1. C/C++ has poor stability, slow compilation speed, and fatal flaws 2. Go does not support hot changes, does not support generics, poor reconfigurability, can not share code with the client, a fatal flaw 3. poor support for Java concurrency, unable to share code with the client 4. Lua has few libraries, poor performance, poor code readability and reconfigurability, cross-platform dependence on C/C++, troublesome to handle, and poor ide support 5. Python poor performance, poor code readability and reconfigurability, unable to share code with clients, poor ide support 6. Erlang performance is poor, functional style is not easy to get started, ide support is poor 7. C# .net core is very good in every convenience, but can't share code with UE4 Currently Unity is the hottest game engine, C# server with Unity is a perfect match, basically can not find defects. ================================================ FILE: Book/1.2为什么使用.net core.md ================================================ # 为什么使用C# .net core做服务端? 游戏服务端从早期的单服到分布式,开发越来越复杂,对稳定性,开发效率要求越来越高。开发语言的选择也逐步发生了变化,C 到 C++ 到 C++ + PYTHON 或者C++ + LUA 到现在 很多公司开始使用erlang,go,java,c#。目前是一个百花齐放的局面。 但是如果是要你重新做一个网游server,不考虑对公司或者已有的东西兼容性,你会怎么选择?我仔细想了一下这个问题,大概有这个几个方面需要考虑: ###### 1. 语言的稳定性(致命性) 游戏服务器的特点是高负载低延时。所以一般服务端进程都是带状态的,一旦挂掉就意味着数据丢失,这点是无法容忍的。 ###### 2. 运行时热更(致命性) 游戏服务器逻辑极其复杂,很容易出现bug,但是又不能经常停服,所以热更修复bug就显得十分必要。出现错误开发人员可以立即编写代码,然后热更修复,线上用户完全感觉不到。 ###### 3. 是否有协程支持(重要性5星) 分布式服务器架构,进程与进程之间必然会有大量交互。由于游戏逻辑很难拆分成多线程,所以一般来说都是逻辑单线程。如果没有协程支持,必然产生大量回调,代码维护会变得非常困难。 ###### 4. 编译速度(重要性5星) 使用c++开发,30%的时间都浪费在编译上。假如编译很快或者不需要编译,必定大大提高开发效率。 ###### 5. 跨平台(4星) 一般游戏服务器都架设在linux上面。但是平常开发,使用windows会更加方便,如果跨平台,开发以及测试效率会大大提升,并且不需要单独搞一个开发机,本机电脑就可以满足平常开发 ###### 6. 可阅读性,可重构性(3星) 代码可以重构能大大减轻写代码的难度 ###### 7. 库是否齐全,生态是否完善(3星) 库齐全,生态好,自己需要造的轮子就少 ###### 8.跟客户端统一语言(3星) 客户端服务端共用语言,优势十分明显,很多代码可以复用,逻辑程序员不再需要区分前后端,双端都可以写,一个人即可完成一个功能,大大减少了沟通的时间成本。 ###### 9. IDE的支持(3星) 代码提示,重构等支持,优秀的IDE能提高几倍的开发效率。 ###### 10. 语言的性能(1星) 目前服务器性能都不是太大问题,不过性能好总比性能差要强。 | 语言 | C# | C/C++ | Java | Go | Lua | Python | Erlang | | -- | :--: | :--: | :--: | :--: | :--: | :--: | :--: | | 稳定性 | 稳定 | 容易挂 | 稳定 | 稳定 | 稳定 | 稳定 | 稳定 | | 运行时热更 | 支持 | 较难支持 | 支持 | 不支持 | 支持 | 支持 | 支持 | | 跨平台 | 支持 | 较难支持 | 支持 | 支持 | 较难支持 | 支持 | 支持 | | 协程 | 有 | 需要自己实现 | 支持不好 | 支持 | 支持 | 支持 | 支持 | | 编译速度 | 快 | 慢 | 快 | 快 | 不需要编译 | 不需要编译 | 快| | 阅读性重构性 | 好 | 一般 | 好 | 一般 | 差 | 差 | 差| | 游戏库跟生态 | 好 | 好 | 一般 | 一般 | 差 | 好 | 一般| | 客户端统一语言 | Unity | Unity、UE4 | 暂无 | 暂无 | Unity、UE4 | UE4 | 暂无| | IDE的支持 | 好 | 好 |好 | 普通| 差 | 差 | 差| | 语言的性能 | 好|极好| 好| 好| 差|很差|差| 从表格可以看出: 1. C/C++稳定性差,编译速度慢,存在致命缺陷 2. Go不支持热更,由于不支持泛型,重构性较差,无法跟客户端共享代码,存在致命缺陷 3. Java协程支持差,无法跟客户端共享代码 4. Lua库少,性能差,代码可阅读性可重构性差,跨平台完全依赖C/C++,处理起来麻烦,ide支持差 5. Python 性能很差,代码可阅读性可重构性差,无法跟客户端共享代码,ide支持差 6. Erlang 性能差,函数式风格不好上手,ide支持差 7. C# .net core各个方便都非常优秀,不过跟UE4无法共享代码 当前Unity是最火的游戏引擎,C#服务端搭配Unity完全是天作之合,基本上找不到缺陷。 ================================================ FILE: Book/2.1CSharp Coroutine.md ================================================ # What is a concurrent thread Speaking of concurrency, let's first understand what is asynchronous. Asynchronous simply means that I want to initiate a call, but the called party (may be other threads, or may be IO) will take some time to produce the result, and I don't want the call to block the entire thread of the caller, so I pass a callback function to the called party, and the called party will call back this callback function after it finishes running to notify the caller to continue The callback function will notify the caller to continue execution. As an example: In the following code, the main thread keeps looping, sleep 1 millisecond per loop, add one to the count, and print once every 10,000 times. ```csharp private static void Main() { int loopCount = 0; while (true) { int temp = watcherValue; Thread.Sleep(1); ++loopCount; if (loopCount % 10000 == 0) { Console.WriteLine($"loop count: {loopCount}"); } } } ``` At this point I need to add a function, at the beginning of the program, I want to print the value of loopCount after 5 seconds. Seeing that after 5 seconds we can think of the Sleep method, which will block the thread for a certain amount of time and then continue execution. We obviously can't Sleep in the main thread because it would break the logic of printing once every 10000 counts. ```csharp // example2_1 class Program { private static int loopCount = 0; private static void Main() { OneThreadSynchronizationContext _ = OneThreadSynchronizationContext.Instance; WaitTimeAsync(5000, WaitTimeFinishCallback); while (true) { OneThreadSynchronizationContext.Instance.Update(); Thread.Sleep(1); ++loopCount; if (loopCount % 10000 == 0) { Console.WriteLine($"loop count: {loopCount}"); } } } private static void WaitTimeAsync(int waitTime, Action action) { Thread thread = new Thread(()=>WaitTime(waitTime, action)); thread.Start(); } private static void WaitTimeFinishCallback() { Console.WriteLine($"WaitTimeAsync finsih loopCount value is: {loopCount}"); } /// /// Waiting in another thread /// private static void WaitTime(int waitTime, Action action) { Thread.Sleep(waitTime); // Throw the action back to the main thread for execution OneThreadSynchronizationContext.Instance.Post((o)=>action(), null); } } ``` We have designed a WaitTimeAsync method here. WaitTimeAsync is actually a typical asynchronous method that initiates a call from the main thread, passes in a WaitTimeFinishCallback method as an argument, opens a thread, and after the thread Sleeps for a certain amount of time, throws the passed callback back to the main thread for execution. OneThreadSynchronizationContext is a cross-thread queue, any thread can throw delegates into it, the Update method of OneThreadSynchronizationContext is called in the main thread and will take out these delegates and put them into the main thread for execution. Why does the callback method need to be thrown back to the main thread for execution? Because the loopCount is read in the callback method, and the loopCount is also read and written in the main thread, so either add a lock or always ensure that it is only read and written in the main thread. It is a bad practice to add locks, and having locks all over the code makes it difficult to read and maintain, and it is easy to create multi-threaded bugs. this is a common technique in multi-threaded development to package the logic into a delegate and throw it back to another thread. We may have a new requirement, after the execution of WaitTimeFinishCallback is finished, we want to wait for 3 seconds and print the loopCount again. ```csharp private static void WaitTimeAsync(int waitTime, Action action) { Thread thread = new Thread(()=>WaitTime(waitTime, action)); thread.Start(); } private static void WaitTimeFinishCallback() { Console.WriteLine($"WaitTimeAsync finsih loopCount value is: {loopCount}"); WaitTimeAsync(3000, WaitTimeFinishCallback2); } private static void WaitTimeFinishCallback2() { Console.WriteLine($"WaitTimeAsync finsih loopCount value is: {loopCount}"); } ``` We may also change the requirement at this point, we need to print the loopCount 5 seconds after the program starts, then 4 seconds after, then 3 seconds after, that is, insert another 3 seconds wait in the middle of the above logic. ```csharp private static void WaitTimeAsync(int waitTime, Action action) { Thread thread = new Thread(()=>WaitTime(waitTime, action)); thread.Start(); } private static void WaitTimeFinishCallback() { Console.WriteLine($"WaitTimeAsync finsih loopCount value is: {loopCount}"); WaitTimeAsync(4000, WaitTimeFinishCallback3); } private static void WaitTimeFinishCallback3() { Console.WriteLine($"WaitTimeAsync finsih loopCount value is: {loopCount}"); WaitTimeAsync(3000, WaitTimeFinishCallback2); } private static void WaitTimeFinishCallback2() { Console.WriteLine($"WaitTimeAsync finsih loopCount value is: {loopCount}"); } ``` This inserts a piece of code in the middle, which seems very cumbersome. Here you can answer what is a concurrent process, in fact this string of callbacks is a concurrent process. ================================================ FILE: Book/2.1CSharp的协程.md ================================================ # 什么是协程 说到协程,我们先了解什么是异步,异步简单说来就是,我要发起一个调用,但是这个被调用方(可能是其它线程,也可能是IO)出结果需要一段时间,我不想让这个调用阻塞住调用方的整个线程,因此传给被调用方一个回调函数,被调用方运行完成后回调这个回调函数就能通知调用方继续往下执行。举个例子: 下面的代码,主线程一直循环,每循环一次sleep 1毫秒,计数加一,每10000次打印一次。 ```csharp private static void Main() { int loopCount = 0; while (true) { int temp = watcherValue; Thread.Sleep(1); ++loopCount; if (loopCount % 10000 == 0) { Console.WriteLine($"loop count: {loopCount}"); } } } ``` 这时我需要加个功能,在程序一开始,我希望在5秒钟之后打印出loopCount的值。看到5秒后我们可以想到Sleep方法,它会阻塞线程一定时间然后继续执行。我们显然不能在主线程中Sleep,因为会破坏掉每10000次计数打印一次的逻辑。 ```csharp // example2_1 class Program { private static int loopCount = 0; private static void Main() { OneThreadSynchronizationContext _ = OneThreadSynchronizationContext.Instance; WaitTimeAsync(5000, WaitTimeFinishCallback); while (true) { OneThreadSynchronizationContext.Instance.Update(); Thread.Sleep(1); ++loopCount; if (loopCount % 10000 == 0) { Console.WriteLine($"loop count: {loopCount}"); } } } private static void WaitTimeAsync(int waitTime, Action action) { Thread thread = new Thread(()=>WaitTime(waitTime, action)); thread.Start(); } private static void WaitTimeFinishCallback() { Console.WriteLine($"WaitTimeAsync finsih loopCount的值是: {loopCount}"); } /// /// 在另外的线程等待 /// private static void WaitTime(int waitTime, Action action) { Thread.Sleep(waitTime); // 将action扔回主线程执行 OneThreadSynchronizationContext.Instance.Post((o)=>action(), null); } } ``` 我们在这里设计了一个WaitTimeAsync方法,WaitTimeAsync其实就是一个典型的异步方法,它从主线程发起调用,传入了一个WaitTimeFinishCallback回调方法做参数,开启了一个线程,线程Sleep一定时间后,将传过来的回调扔回到主线程执行。OneThreadSynchronizationContext是一个跨线程队列,任何线程可以往里面扔委托,OneThreadSynchronizationContext的Update方法在主线程中调用,会将这些委托取出来放到主线程执行。为什么回调方法需要扔回到主线程执行呢?因为回调方法中读取了loopCount,loopCount在主线程中也有读写,所以要么加锁,要么永远保证只在主线程中读写。加锁是个不好的做法,代码中到处是锁会导致阅读跟维护困难,很容易产生多线程bug。这种将逻辑打包成委托然后扔回另外一个线程是多线程开发中常用的技巧。 我们可能又有个新需求,WaitTimeFinishCallback执行完成之后,再想等3秒,再打印一下loopCount。 ```csharp private static void WaitTimeAsync(int waitTime, Action action) { Thread thread = new Thread(()=>WaitTime(waitTime, action)); thread.Start(); } private static void WaitTimeFinishCallback() { Console.WriteLine($"WaitTimeAsync finsih loopCount的值是: {loopCount}"); WaitTimeAsync(3000, WaitTimeFinishCallback2); } private static void WaitTimeFinishCallback2() { Console.WriteLine($"WaitTimeAsync finsih loopCount的值是: {loopCount}"); } ``` 我们这时还可能改需求,需要在程序启动5秒后,接下来4秒,再接下来3秒,打印loopCount,也就是上面的逻辑中间再插入一个3秒等待。 ```csharp private static void WaitTimeAsync(int waitTime, Action action) { Thread thread = new Thread(()=>WaitTime(waitTime, action)); thread.Start(); } private static void WaitTimeFinishCallback() { Console.WriteLine($"WaitTimeAsync finsih loopCount的值是: {loopCount}"); WaitTimeAsync(4000, WaitTimeFinishCallback3); } private static void WaitTimeFinishCallback3() { Console.WriteLine($"WaitTimeAsync finsih loopCount的值是: {loopCount}"); WaitTimeAsync(3000, WaitTimeFinishCallback2); } private static void WaitTimeFinishCallback2() { Console.WriteLine($"WaitTimeAsync finsih loopCount的值是: {loopCount}"); } ``` 这样中间插入一段代码,显得非常麻烦。这里可以回答什么是协程了,其实这一串串回调就是协程。 ================================================ FILE: Book/2.2Better Coroutine.md ================================================ # Better Coroutine The above article talks about how a string of callbacks is a concurrent process, and obviously writing code this way, adding logic and inserting logic is very error prone. We need to use asynchronous syntax to change the form of this asynchronous callback to a synchronous form, fortunately C# has been designed for us, see the code ```csharp // example2_2 class Program { private static int loopCount = 0; static void Main(string[] args) { OneThreadSynchronizationContext _ = OneThreadSynchronizationContext.Instance; Console.WriteLine($"Main Thread: {Thread.CurrentThread.ManagedThreadId}"); Crontine(); while (true) { OneThreadSynchronizationContext.Instance.Update(); Thread.Sleep(1); ++loopCount; if (loopCount % 10000 == 0) { Console.WriteLine($"loop count: {loopCount}"); } } } private static async void Crontine() { await WaitTimeAsync(5000); Console.WriteLine($"Current thread: {Thread.CurrentThread.ManagedThreadId}, WaitTimeAsync finsih loopCount's value is: {loopCount}"); await WaitTimeAsync(4000); Console.WriteLine($"Current Thread: {Thread.CurrentThread.ManagedThreadId}, WaitTimeAsync finsih The value of loopCount is: {loopCount}"); await WaitTimeAsync(3000); Console.WriteLine($"Current Thread: {Thread.CurrentThread.ManagedThreadId}, WaitTimeAsync finsih The value of loopCount is: {loopCount}"); } private static Task WaitTimeAsync(int waitTime) { TaskCompletionSource tcs = new TaskCompletionSource(); Thread thread = new Thread(()=>WaitTime(waitTime, tcs)); thread.Start(); return tcs.Task; } /// /// Waiting in another thread /// private static void WaitTime(int waitTime, TaskCompletionSource tcs) { Thread.Sleep(waitTime); // throw tcs back to the main thread for execution OneThreadSynchronizationContext.Instance.Post(o=>tcs.SetResult(true), null); } } ``` In this code, in the WaitTimeAsync method, we use the TaskCompletionSource class instead of the previously passed Action parameter, and the WaitTimeAsync method returns a Task type result. waitTime we replace action() with tcs. SetResult(true), and the WaitTimeAsync method uses the await keyword in front of it, so that the sequence of callbacks can be changed to a synchronized form. This makes the code look very simple and much easier to develop. Here is another trick, we found that WaitTime needs to throw tcs.SetResult back to the main thread for execution, Microsoft gives us a simple way to set up the synchronization context in the main thread by referring to example2_2_2 ```csharp // example2_2_2 SynchronizationContext.SetSynchronizationContext(OneThreadSynchronizationContext.Instance); ``` SetResult(true) will be called directly in WaitTime, the callback will be automatically thrown to the synchronization context, and the synchronization context we can take out in the main thread to execute the callback, so automatically able to complete the operation back to the main thread ```csharp private static void WaitTime(int waitTime, TaskCompletionSource tcs) { Thread.Sleep(waitTime); tcs.SetResult(true); } ``` If you do not set the synchronization context, you will find that the printout of the current thread is not the main thread, which is also the use of many third-party libraries and . In fact, I think this design is not necessary, to the library developers to achieve better, especially in the game development, logic is all single-threaded, callback every time you go through the synchronization context is redundant, so the ET framework provides the implementation of ETTask does not use the synchronization context, the code is more concise and efficient, which will be discussed later. ================================================ FILE: Book/2.2更好的协程.md ================================================ # 更好的协程 上文讲了一串回调就是协程,显然这样写代码,增加逻辑,插入逻辑非常容易出错。我们需要利用异步语法把这个异步回调的形式改成同步的形式,幸好C#已经帮我们设计好了,看代码 ```csharp // example2_2 class Program { private static int loopCount = 0; static void Main(string[] args) { OneThreadSynchronizationContext _ = OneThreadSynchronizationContext.Instance; Console.WriteLine($"主线程: {Thread.CurrentThread.ManagedThreadId}"); Crontine(); while (true) { OneThreadSynchronizationContext.Instance.Update(); Thread.Sleep(1); ++loopCount; if (loopCount % 10000 == 0) { Console.WriteLine($"loop count: {loopCount}"); } } } private static async void Crontine() { await WaitTimeAsync(5000); Console.WriteLine($"当前线程: {Thread.CurrentThread.ManagedThreadId}, WaitTimeAsync finsih loopCount的值是: {loopCount}"); await WaitTimeAsync(4000); Console.WriteLine($"当前线程: {Thread.CurrentThread.ManagedThreadId}, WaitTimeAsync finsih loopCount的值是: {loopCount}"); await WaitTimeAsync(3000); Console.WriteLine($"当前线程: {Thread.CurrentThread.ManagedThreadId}, WaitTimeAsync finsih loopCount的值是: {loopCount}"); } private static Task WaitTimeAsync(int waitTime) { TaskCompletionSource tcs = new TaskCompletionSource(); Thread thread = new Thread(()=>WaitTime(waitTime, tcs)); thread.Start(); return tcs.Task; } /// /// 在另外的线程等待 /// private static void WaitTime(int waitTime, TaskCompletionSource tcs) { Thread.Sleep(waitTime); // 将tcs扔回主线程执行 OneThreadSynchronizationContext.Instance.Post(o=>tcs.SetResult(true), null); } } ``` 在这段代码里面,WaitTimeAsync方法中,我们利用了TaskCompletionSource类替代了之前传入的Action参数,WaitTimeAsync方法返回了一个Task类型的结果。WaitTime中我们把action()替换成了tcs.SetResult(true),WaitTimeAsync方法前使用await关键字,这样可以将一连串的回调改成同步的形式。这样一来代码显得十分简洁,开发起来也方便多了。 这里还有个技巧,我们发现WaitTime中需要将tcs.SetResult扔回到主线程执行,微软给我们提供了一种简单的方法,参考example2_2_2,在主线程设置好同步上下文, ```csharp // example2_2_2 SynchronizationContext.SetSynchronizationContext(OneThreadSynchronizationContext.Instance); ``` 在WaitTime中直接调用tcs.SetResult(true)就行了,回调会自动扔到同步上下文中,而同步上下文我们可以在主线程中取出回调执行,这样自动能够完成回到主线程的操作 ```csharp private static void WaitTime(int waitTime, TaskCompletionSource tcs) { Thread.Sleep(waitTime); tcs.SetResult(true); } ``` 如果不设置同步上下文,你会发现打印出来当前线程就不是主线程了,这也是很多第三方库跟.net core内置库的用法,默认不回调到主线程,所以我们使用的时候需要设置下同步上下文。其实这个设计本人觉得没有必要,交由库的开发者去实现更好,尤其是在游戏开发中,逻辑全部是单线程的,回调每次都走一遍同步上下文就显得多余了,所以ET框架提供了不使用同步上下文的实现ETTask,代码更加简洁更加高效,这个后面会讲到。 ================================================ FILE: Book/2.3Single-threaded asynchronous.md ================================================ # Single-threaded asynchronous The previous examples are multi-threaded implementations of asynchrony, but asynchrony is obviously not just multi-threaded. We used Sleep in the previous examples to achieve time waiting, each timer needs to use a thread, which will lead to frequent thread switching, this implementation is very inefficient, usually will not do so. General game logic will design a single-threaded timer, we do a simple implementation here, used to explain single-threaded asynchronous. ```csharp // example2_3 class Program { private static int loopCount = 0; private static long time; private static Action action; static void Main(string[] args) { Console.WriteLine($"Main thread: {Thread.CurrentThread.ManagedThreadId}"); Crontine(); while (true) { Thread.Sleep(1); CheckTimerOut(); ++loopCount; if (loopCount % 10000 == 0) { Console.WriteLine($"loop count: {loopCount}"); } } } private static void Crontine() { WaitTimeAsync(5000, WaitTimeAsyncCallback1); } private static void WaitTimeAsyncCallback1() { Console.WriteLine($"Current thread: {Thread.CurrentThread.ManagedThreadId}, WaitTimeAsync finsih loopCount's value is: {loopCount}"); WaitTimeAsync(4000, WaitTimeAsyncCallback2); } private static void WaitTimeAsyncCallback2() { Console.WriteLine($"Current thread: {Thread.CurrentThread.ManagedThreadId}, WaitTimeAsync finsih loopCount's value is: {loopCount}"); WaitTimeAsync(3000, WaitTimeAsyncCallback3); } private static void WaitTimeAsyncCallback3() { Console.WriteLine($"Current thread: {Thread.CurrentThread.ManagedThreadId}, WaitTimeAsync finsih loopCount's value is: {loopCount}"); } private static void CheckTimerOut() { if (time == 0) { return; } long nowTicks = DateTime.Now.Ticks / 10000; if (time > nowTicks) { return; } return; } time = 0; action.Invoke(); } private static void WaitTimeAsync(int waitTime, Action a) { Ticks / 10000 + waitTime; action = a; } } ``` This example also implements a simple timing method, WaitTimeAsync will be called to record the callback method and time, the main thread will call CheckTimerOut every frame, CheckTimerOut inside to determine whether the timer is expired, expired then callback method is called. The whole logic is done in the main thread, also asynchronously. So asynchronous is not multi-threaded, single-threaded can also be asynchronous. The above example can be changed to await as well. ```csharp // example2_3_2 class Program { private static int loopCount = 0; private static long time; private static TaskCompletionSource tcs; static void Main(string[] args) { Console.WriteLine($"Main thread: {Thread.CurrentThread.ManagedThreadId}"); Crontine(); while (true) { Thread.Sleep(1); CheckTimerOut(); ++loopCount; if (loopCount % 10000 == 0) { Console.WriteLine($"loop count: {loopCount}"); } } } private static async void Crontine() { await WaitTimeAsync(5000); Console.WriteLine($"Current thread: {Thread.CurrentThread.ManagedThreadId}, WaitTimeAsync finsih loopCount's value is: {loopCount}"); await WaitTimeAsync(4000); Console.WriteLine($"Current Thread: {Thread.CurrentThread.ManagedThreadId}, WaitTimeAsync finsih The value of loopCount is: {loopCount}"); await WaitTimeAsync(3000); Console.WriteLine($"Current Thread: {Thread.CurrentThread.ManagedThreadId}, WaitTimeAsync finsih The value of loopCount is: {loopCount}"); } private static void CheckTimerOut() { if (time == 0) { return; } long nowTicks = DateTime.Now.Ticks / 10000; if (time > nowTicks) { return; } return; } time = 0; tcs.SetResult(true); } private static Task WaitTimeAsync(int waitTime) { TaskCompletionSource t = new TaskCompletionSource(); Time = DateTime.Now.Ticks / 10000 + waitTime; tcs = t; return t.Task; } } ``` The above example all calls are done in the main thread and use await, so await does not open multithreading, await specific use of multithreading depends entirely on the specific implementation ================================================ FILE: Book/2.3单线程异步.md ================================================ # 单线程异步 前面几个例子都是多线程实现的异步,但是异步显然不仅仅是多线程的。我们在之前的例子中使用了Sleep来实现时间的等待,每一个计时器都需要使用一个线程,会导致线程切换频繁,这个实现效率很低,平常是不会这样做的。一般游戏逻辑中会设计一个单线程的计时器,我们这里做一个简单的实现,用来讲解单线程异步。 ```csharp // example2_3 class Program { private static int loopCount = 0; private static long time; private static Action action; static void Main(string[] args) { Console.WriteLine($"主线程: {Thread.CurrentThread.ManagedThreadId}"); Crontine(); while (true) { Thread.Sleep(1); CheckTimerOut(); ++loopCount; if (loopCount % 10000 == 0) { Console.WriteLine($"loop count: {loopCount}"); } } } private static void Crontine() { WaitTimeAsync(5000, WaitTimeAsyncCallback1); } private static void WaitTimeAsyncCallback1() { Console.WriteLine($"当前线程: {Thread.CurrentThread.ManagedThreadId}, WaitTimeAsync finsih loopCount的值是: {loopCount}"); WaitTimeAsync(4000, WaitTimeAsyncCallback2); } private static void WaitTimeAsyncCallback2() { Console.WriteLine($"当前线程: {Thread.CurrentThread.ManagedThreadId}, WaitTimeAsync finsih loopCount的值是: {loopCount}"); WaitTimeAsync(3000, WaitTimeAsyncCallback3); } private static void WaitTimeAsyncCallback3() { Console.WriteLine($"当前线程: {Thread.CurrentThread.ManagedThreadId}, WaitTimeAsync finsih loopCount的值是: {loopCount}"); } private static void CheckTimerOut() { if (time == 0) { return; } long nowTicks = DateTime.Now.Ticks / 10000; if (time > nowTicks) { return; } time = 0; action.Invoke(); } private static void WaitTimeAsync(int waitTime, Action a) { time = DateTime.Now.Ticks / 10000 + waitTime; action = a; } } ``` 这个例子同样实现了一个简单的计时方法,WaitTimeAsync调用时会将回调方法跟时间记录下来,主线程每帧都会调用CheckTimerOut,CheckTimerOut里面判断计时器是否过期,过期了则调用回调方法。整个逻辑都在主线程中完成,同样是异步方法。所以异步并非多线程,单线程同样可以异步。上面的例子同样可以改成await的形式。 ```csharp // example2_3_2 class Program { private static int loopCount = 0; private static long time; private static TaskCompletionSource tcs; static void Main(string[] args) { Console.WriteLine($"主线程: {Thread.CurrentThread.ManagedThreadId}"); Crontine(); while (true) { Thread.Sleep(1); CheckTimerOut(); ++loopCount; if (loopCount % 10000 == 0) { Console.WriteLine($"loop count: {loopCount}"); } } } private static async void Crontine() { await WaitTimeAsync(5000); Console.WriteLine($"当前线程: {Thread.CurrentThread.ManagedThreadId}, WaitTimeAsync finsih loopCount的值是: {loopCount}"); await WaitTimeAsync(4000); Console.WriteLine($"当前线程: {Thread.CurrentThread.ManagedThreadId}, WaitTimeAsync finsih loopCount的值是: {loopCount}"); await WaitTimeAsync(3000); Console.WriteLine($"当前线程: {Thread.CurrentThread.ManagedThreadId}, WaitTimeAsync finsih loopCount的值是: {loopCount}"); } private static void CheckTimerOut() { if (time == 0) { return; } long nowTicks = DateTime.Now.Ticks / 10000; if (time > nowTicks) { return; } time = 0; tcs.SetResult(true); } private static Task WaitTimeAsync(int waitTime) { TaskCompletionSource t = new TaskCompletionSource(); time = DateTime.Now.Ticks / 10000 + waitTime; tcs = t; return t.Task; } } ``` 上面这个例子所有调用全部在主线程中完成,并且使用了await,因此await并不会开启多线程,await具体用没用多线程完全取决于具体的实现 ================================================ FILE: Book/3.2The powerful MongoBson library.md ================================================ # The powerful MongoBson library Back-end development, statistics about these scenarios need to use serialization: 1. 1. object clone by serialization deserialization 2. server-side database storage data, binary 3. distributed server-side, multi-process messages, binary 4. back-end logs, text format 5. various server-side configuration files, text format There are very, very many C# serialization libraries, protobuf, json and so on. But these serialization libraries should not be readable and small for all scenarios. protobuf does not support complex object structures (cannot use inheritance) and is suitable for messages, but not for database storage and log formats. json is suitable for log formats, but is too big for network messages and data storage. We certainly want a library that can satisfy all the above scenarios for the following reasons. 1. you think about one day your configuration file needs to be saved in the database, you do not need to do the format conversion, the back-end directly to the configuration message sent by the front-end to save to the database, which can not reduce very many errors? 2. one day some server-side configuration files do not use the file format, need to be placed in the database, again, only a few lines of code to complete the migration. 3. one day the back-end server crash, you need to scan the logs for data recovery, the logs for deserialization into C# objects, one by one for processing, and then into objects saved to the database is complete. 4. objects saved in the database, you can directly see the text content, you can do a variety of SQL-like operations 5. Imagine a scenario where a configuration text object, deserialized into memory, sent via network messages, and stored in the database. The whole process is done in one go. Simply put, it reduces various data conversions, reduces code, improves development efficiency, and improves maintainability. MongoDB library can serialize both text and BSON binary format, and MongoDB itself is a very much used database in the game. Its support features are as follows. 1. support complex inheritance structure 2. support for ignoring certain fields serialization 3. support field default values 4. structure with extra fields can still be deserialized, which is very useful for multi-version protocols 5. support for ISupportInitialize interface, this is a godsend when deserialization 6. support for text json and binary bson serialization 7. MongoDB database support A brief introduction to the mongo bson library ### 1. Support serialization deserialization into json or bson ```csharp public sealed class Player { public long Id; public string Account { get; private set; } public long UnitId { get; set; } } Player player1 = new Player() { Id = 1 }; string json = player1.ToJson(); Console.WriteLine($"player1 to json: {json}"); Console.WriteLine($"player to bson: {player.ToBson().ToHex()}"); // output: // player to json: { "_id" : NumberLong(1), "C" : [], "Account" : null, "UnitId" : NumberLong(0) } // player to bson: B000000125F69640001000000000000000A4163636F756E740012556E69744964000000000000000000000000 ``` Note that mongo's json is a bit different from the standard json, if you want to use the standard json, you can pass in a JsonWriterSettings object and restrict the use of JsonOutputMode.Strict mode ```csharp // use standard json Player player2 = new Player() { Id = 1 }; Console.WriteLine($"player to json: {player2.ToJson(new JsonWriterSettings() {OutputMode = JsonOutputMode.Strict})}"); // player to json: { "_id" : 1, "C" : [], "Account" : null, "UnitId" : 0 } ``` Deserialize json: ```csharp // deserialize json Player player11 = BsonSerializer.Deserialize(json); Console.WriteLine($"player11 to json: {player11.ToJson()}"); ``` Deserialize bson: ```csharp // deserialize bson using (MemoryStream memoryStream = new MemoryStream(bson)) { Player player12 = (Player) BsonSerializer.Deserialize(memoryStream, typeof (Player)); Console.WriteLine($"player12 to json: {player12.ToJson()}"); } ``` ### 2. Some fields can be ignored [BsonIgnore] This tag is used to disable field serialization. ```csharp public sealed class Player { public long Id; [BsonIgnore] public string Account { get; private set; } public long UnitId { get; set; } } Player player = new Player() { Id = 2, UnitId = 3, Account = "panda"}; Console.WriteLine($"player to json: {player.ToJson()}"); // player to json: { "_id" : 2, "UnitId" : 3 } ``` ### 3. Support for default values and taking aliases The [BsonElement] field with this tag will serialize even private fields (only public fields are serialized by default), and the tag can take a string parameter to assign an alias to the field serialization. ```csharp public sealed class Player { public long Id; public string Account { get; private set; } [BsonElement("UId")] public long UnitId { get; set; } } Player player = new Player() { Id = 2, UnitId = 3, Account = "panda"}; Console.WriteLine($"player to json: {player.ToJson()}"); // player to json: { "_id" : 2, "Account" : "panda", "UId" : 3 } ``` ### 4. Upgrade version support [BsonIgnoreExtraElements] This tag is used on top of class, used to ignore extra fields when deserializing, general version compatibility needs to be considered, low version of the protocol needs to be able to deserialize otherwise the new version adds fields, the old version structure deserialization will be wrong ```csharp [BsonIgnoreExtraElements] public sealed class Player public public long Id; public string Account { get; private set; } [BsonElement("UId") public long UnitId { get; set; } } ``` ### 5. Support for complex inheritance structures The power of the mongo bson library is that it fully supports serialization deserialization inheritance structures. Note that inheritance deserialization requires registration of all parent classes, and there are two ways to do this. a. You can declare the inherited subclasses on top of the parent class using the [BsonKnownTypes] tag, so that mongo will automatically register them, e.g.: ```csharp [BsonKnownTypes(typeof(Entity))] public class Component { } [BsonKnownTypes(typeof(Player))] public class Entity: Component { } public sealed class Player: Entity { public long Id; public string Account { get; set; } public long UnitId { get; set; } } ``` This is flawed because the framework doesn't know what subclasses a class will have, and this is invasive to the framework code, and we want to uncouple this . You can scan the assembly for the types of all subclass parents and register them with the mongo driver ```csharp Type[] types = typeof(Game).Assembly.GetTypes(); foreach (Type type in types) { if (!type.IsSubclassOf(typeof(Component)))) { continue; } BsonClassMap.LookupClassMap(type); } BsonSerializer.RegisterSerializer(new EnumSerializer(BsonType.String)); ``` This completely automates the registration and the user does not need to relate whether the class is registered or not. ### 6. ISupportInitialize interface mongo bson deserialization supports an ISupportInitialize interface, ISupportInitialize has two methods ```csharp public interface ISupportInitialize { void BeginInit(); void EndInit(); } ``` BeginInit is called before deserialization and EndInit is called after deserialization. This interface is very useful now to perform some operations after deserialization. For example ```csharp [BsonIgnoreExtraElements] public class InnerConfig: AConfigComponent { [BsonIgnore] public IPEndPoint IPEndPoint { get; private set; } public string Address { get; set; } public override void EndInit() { this.IPEndPoint = NetworkHelper.ToIPEndPoint(this.Address); } } ```` InnerConfig is the configuration of the process inner network address in ET. Since IPEndPoint is not very configurable, we can configure it as a string and then convert the string to IPEndPoint in EndInit when deserializing. I also added this call to the protobuf deserialization method, refer to ProtobufHelper.cs, ET's protobuf because to support ilruntime, so remove the map support, if we want a map how to do? Here I gave the generated code are done, the proto messages are changed to a partial class, so that we can extend the class themselves, for example. ```csharp message UnitInfo { int64 UnitId = 1; float X = 2; float Y = 3; float Z = 4; } // protobuf message G2C_EnterMap // IResponse { int32 RpcId = 90; int32 Error = 91; string Message = 92; // own unit id int64 UnitId = 1; // all the units repeated UnitInfo Units = 2; } ``` This network message has a repeated UnitInfo field, which is actually an array in protobuf, which is not very convenient to use, I want to convert it to a Dictionary field, we can do something like this ```csharp public partial class G2C_EnterMap: ISupportInitialize { public Dictionary unitsDict = new Dictionary(); public void BeginInit() { } public void EndInit() { foreach (var unit in this.Units) { this.unitsDict.Add(unit.UnitId, unit); } } } ``` The message is extended by such a piece of code, and after deserialization, it is automatically converted into a Dictionary. ================================================ FILE: Book/3.2强大的MongoBson库.md ================================================ # 强大的MongoBson库 后端开发,统计了一下大概有这些场景需要用到序列化: 1. 对象通过序列化反序列化clone 2. 服务端数据库存储数据,二进制 3. 分布式服务端,多进程间的消息,二进制 4. 后端日志,文本格式 5. 服务端的各种配置文件,文本格式 C#序列化库有非常非常多了,protobuf,json等等。但是这些序列化库都无法应当所有场景,既要可读又要小。protobuf不支持复杂的对象结构(无法使用继承),做消息合适,做数据库存储和日志格式并不好用。json做日志格式合适,但是做网络消息和数据存储就太大。我们当然希望一个库能满足上面所有场景,理由如下: 1. 你想想某天你的配置文件需要放到数据库中保存,你不需要进行格式转换,后端直接把前端发过来的配置消息保存到数据库中,这是不是能减少非常多错误呢? 2. 某天有些服务端的配置文件不用文件格式了,需要放在数据库中,同样,只需要几行代码就可以完成迁移。 3. 某天后端服务器crash,你需要扫描日志进行数据恢复,把日志进行反序列化成C#对象,一条条进行处理,再转成对象保存到数据库就完成了。 4. 对象保存在数据库,直接就可以看到文本内容,可以做各种类sql的操作 5. 想像一个场景,一个配置文本对象,反序列化到内存,通过网络消息发送,存储到数据库中。整个过程一气呵成。 简单来说就是减少各种数据转换,减少代码,提高开发效率,提高可维护性。当然,Mongo Bson就能够满足。MongoDB库既可以序列化成文本也可以序列化成BSON的二进制格式,并且MongoDB本身就是一个游戏中使用非常多的数据库。Mongo Bson非常完善,是我见过功能最全使用最强大的序列化库,有些功能十分贴心。其支持功能如下: 1. 支持复杂的继承结构 2. 支持忽略某些字段序列化 3. 支持字段默认值 4. 结构多出多余的字段照样可以反序列化,这对多版本协议非常有用 5. 支持ISupportInitialize接口使用,这个在反序列化的时候简直就是神器 6. 支持文本json和二进制bson序列化 7. MongoDB数据库支持 简单的介绍下mongo bson库 ### 1.支持序列化反序列化成json或者bson ```csharp public sealed class Player { public long Id; public string Account { get; private set; } public long UnitId { get; set; } } Player player1 = new Player() { Id = 1 }; string json = player1.ToJson(); Console.WriteLine($"player1 to json: {json}"); Console.WriteLine($"player to bson: {player.ToBson().ToHex()}"); // output: // player to json: { "_id" : NumberLong(1), "C" : [], "Account" : null, "UnitId" : NumberLong(0) } // player to bson: B000000125F69640001000000000000000A4163636F756E740012556E6974496400000000000000000000 ``` 注意mongo的json跟标准的json有点区别,如果想用标准的json,可以传入一个JsonWriterSettings对象,限制使用JsonOutputMode.Strict模式 ```csharp // 使用标准json Player player2 = new Player() { Id = 1 }; Console.WriteLine($"player to json: {player2.ToJson(new JsonWriterSettings() {OutputMode = JsonOutputMode.Strict})}"); // player to json: { "_id" : 1, "C" : [], "Account" : null, "UnitId" : 0 } ``` 反序列化json: ```csharp // 反序列化json Player player11 = BsonSerializer.Deserialize(json); Console.WriteLine($"player11 to json: {player11.ToJson()}"); ``` 反序列化bson: ```csharp // 反序列化bson using (MemoryStream memoryStream = new MemoryStream(bson)) { Player player12 = (Player) BsonSerializer.Deserialize(memoryStream, typeof (Player)); Console.WriteLine($"player12 to json: {player12.ToJson()}"); } ``` ### 2.可以忽略某些字段 [BsonIgnore]该标签用来禁止字段序列化。 ```csharp public sealed class Player { public long Id; [BsonIgnore] public string Account { get; private set; } public long UnitId { get; set; } } Player player = new Player() { Id = 2, UnitId = 3, Account = "panda"}; Console.WriteLine($"player to json: {player.ToJson()}"); // player to json: { "_id" : 2, "UnitId" : 3 } ``` ### 3.支持默认值以及取别名 [BsonElement] 字段加上该标签,即使是private字段也会序列化(默认只序列化public字段),该标签还可以带一个string参数,给字段序列化指定别名。 ```csharp public sealed class Player { public long Id; public string Account { get; private set; } [BsonElement("UId")] public long UnitId { get; set; } } Player player = new Player() { Id = 2, UnitId = 3, Account = "panda"}; Console.WriteLine($"player to json: {player.ToJson()}"); // player to json: { "_id" : 2, "Account" : "panda", "UId" : 3 } ``` ### 4.升级版本支持 [BsonIgnoreExtraElements] 该标签用在class上面,反序列化时用来忽略多余的字段,一般版本兼容需要考虑,低版本的协议需要能够反 序列化高版本的内容,否则新版本加了字段,旧版本结构反序列化会出错 ```csharp [BsonIgnoreExtraElements] public sealed class Player { public long Id; public string Account { get; private set; } [BsonElement("UId")] public long UnitId { get; set; } } ``` ### 5.支持复杂的继承结构 mongo bson库强大的地方在于完全支持序列化反序列化继承结构。需要注意的是,继承反序列化需要注册所有的父类,有两种方法: a. 你可以在父类上面使用[BsonKnownTypes]标签声明继承的子类,这样mongo会自动注册,例如: ```csharp [BsonKnownTypes(typeof(Entity))] public class Component { } [BsonKnownTypes(typeof(Player))] public class Entity: Component { } public sealed class Player: Entity { public long Id; public string Account { get; set; } public long UnitId { get; set; } } ``` 这样有缺陷,因为框架并不知道一个类会有哪些子类,这样做对框架代码有侵入性,我们希望能解除这个耦合 。可以扫描程序集中所有子类父类的类型,将他们注册到mongo驱动中 ```csharp Type[] types = typeof(Game).Assembly.GetTypes(); foreach (Type type in types) { if (!type.IsSubclassOf(typeof(Component))) { continue; } BsonClassMap.LookupClassMap(type); } BsonSerializer.RegisterSerializer(new EnumSerializer(BsonType.String)); ``` 这样完全的自动化注册,使用者也不需要关心类是否注册。 ### 6.ISupportInitialize接口 mongo bson反序列化时支持一个ISupportInitialize接口,ISupportInitialize有两个方法 ```csharp public interface ISupportInitialize { void BeginInit(); void EndInit(); } ``` BeginInit在反序列化前调用,EndInit在反序列化后调用。这个接口非常有用了,可以在反序列化后执行一些操作。例如 ```csharp [BsonIgnoreExtraElements] public class InnerConfig: AConfigComponent { [BsonIgnore] public IPEndPoint IPEndPoint { get; private set; } public string Address { get; set; } public override void EndInit() { this.IPEndPoint = NetworkHelper.ToIPEndPoint(this.Address); } } ``` InnerConfig是ET中进程内网地址的配置,由于IPEndPoint不太好配置,我们可以配置成string形式,然后反序列化的时候在EndInit中把string转换成IPEndPoint。 同样我给protobuf反序列化方法也加上了这个调用,参考ProtobufHelper.cs,ET的protobuf因为要支持ilruntime,所以去掉了map的支持,假如我们想要一个map怎么办呢?这里我给生成的代码都做了手脚,把proto消息都改成了partial class,这样我们可以自己扩展这个class,比如: ```csharp message UnitInfo { int64 UnitId = 1; float X = 2; float Y = 3; float Z = 4; } // protobuf message G2C_EnterMap // IResponse { int32 RpcId = 90; int32 Error = 91; string Message = 92; // 自己的unit id int64 UnitId = 1; // 所有的unit repeated UnitInfo Units = 2; } ``` 这个网络消息有个repeated UnitInfo字段,在protobuf中其实是个数组,使用起来不是很方便,我希望转成一个Dictionary的字段,我们可以做这样的操作: ```csharp public partial class G2C_EnterMap: ISupportInitialize { public Dictionary unitsDict = new Dictionary(); public void BeginInit() { } public void EndInit() { foreach (var unit in this.Units) { this.unitsDict.Add(unit.UnitId, unit); } } } ``` 通过这样一段代码把消息进行扩展一下,反序列化出来之后,自动转成了一个Dictionary。 ================================================ FILE: Book/3.3Everything is Entity.md ================================================ # Everything is Entity The ECS design is very popular right now, mainly because of the success of Watchtower, which has led to the explosion of this technology. The most important design of ECS is the complete separation of logic and data. That is, EC is pure data, System is actually logic, by data-driven logic. What does data-driven logic mean? It is very simple to detect data changes through Update and subscribe to data changes through the event mechanism, which is called data-driven. Other features such as cache hits are not too important in writing logic, modern games are scripted, even the performance of the script can tolerate how will care about cache hits that performance improvement? For example, in order to reuse, the data must be split into very small particles, which will lead to a very large number of components. But the game is developed cooperatively by multiple people, each person is basically only familiar with their own modules, which may end up causing a large number of redundant components. Another problem is that the common ECS is flat, with only one layer of Entity and Component. It is like a company, the biggest is the boss, the boss with hundreds of people under him, the boss can not know all the people, to complete a task, the boss can not pick out the people they need. A reasonable approach is to have several managers under the boss, several supervisors under each manager, and several workers under each supervisor, so as to form a tree-like management structure that is easy to manage. This is similar to the ET approach, where Entity can manage Component, Component can manage Entity, and even Component can mount Component. e.g. a person is composed of head, body, hands, feet, and the head is composed of eyes, ears, nose, mouth. ```csharp Head head = human.AddComponent(); head.AddComponent(); head.AddComponent(); AddComponent(); AddComponent(); human.AddComponent(); AddComponent(); human.AddComponent(); ``` In ET, all data is Entity, including Entity, and Entity can be used either as a component or as a child of other Entity. Generic data is placed on Entity as a member, and less generic data can be hung on Entity as a component. For example, the design of items, all items have fields configured id, quantity, level, these fields are not necessary to make components, put on Entity will be more convenient to use. ```csharp class Item: Entity { // The configuration Id of the props public int ConfigId { get; set; } // The number of props public int Count { get; set; } // The level of the props public int Level { get; set; } } ``` This design data of ET is a kind of tree structure, very hierarchical and can understand the whole game architecture very easily. Scene, and the data of different modules are mounted on top of Game. Scene, and each module can mount a lot of data under itself. You don't have to think too much about how to design the classes, where to put the data, whether mounting here will lead to redundancy, etc. for each new feature. For example, if my player needs to do a prop system, I can design an ItemsComponent to be mounted on the Player, and I need to develop a SpellComponent to be mounted on the Player for skills. If the whole service needs to do an activity, get an activity component to hang on top of the Game. This design will be very easy to assign tasks and very modular. # Some details of the component ### 1. Component creation ComponentFactory provides three methods to create components Create, CreateWithParent, CreateWithId. Create is the simplest way to create components, it does several things a. Construct a component based on the component type b. Add the component to the event system and throw an AwakeSystem c. Enables object pooling or not CreateWithParent provides a Parent object on top of Create, which is set to the Component.Parent field. createWithId is used to create ComponentWithId or its subclasses, and you can set an Id on top of Create itself, Component can choose whether to use a pool of objects when it is created. All three types of factory methods have a fromPool parameter, the default is true. ### 2. Release of components Component inherits an IDisposable interface. It should be noted that Component has unmanaged resources and must be called to delete a Component. This interface does the following a. Throw Destroy System b. If the component was created using an object pool, then it is put back into the object pool here c. Remove the component from the global EventSystem and set the InstanceId to 0 If the component is mounted on Entity, then when Entity calls Dispose, it will automatically call the Dispose method of all components on it. ### 3. Role of InstanceId Any Component comes with an InstanceId field, which is reset when the component is constructed, or when the component is removed from the object pool, and which identifies the identity of the component. Why is such a field needed? There are several reasons 1. the existence of the object pool, the component may not be released, but returned to the object pool. In an asynchronous call, it is likely that the component has already been released and then reused, so that we need a way to be able to distinguish whether the previous component object has been released, such as the following code. ```csharp public static async ETVoid UpdateAsync(this ActorLocationSender self) { try { long instanceId = self.InstanceId; while (true) { if (self.InstanceId ! = instanceId) { return; } ActorTask actorTask = await self.GetAsync(); if (self.InstanceId ! = instanceId) { return; } if (actorTask.ActorRequest == null) { return; } await self.RunTask(actorTask); } } catch (Exception e) { Log.Error(e); } } ``` While (true) is an asynchronous method, after await self.GetAsync() it is likely that the ActorLocationSender object has been released, and it is even possible that the object has been utilized again by other logic from the object pool. We can determine whether the object has been released by the change of InstanceId. 2. 2. InstanceId is globally unique and has location information, so you can find the location of the object by InstanceId and send the message to the object. This design will be utilized in the Actor message. Here for the time being will not talk about it. ================================================ FILE: Book/3.3一切皆实体.md ================================================ # 一切皆实体 目前十分流行ECS设计,主要是守望先锋的成功,引爆了这种技术。守望先锋采用了状态帧这种网络技术,客户端会进行预测,预测不准需要进行回滚,由于组件式的设计,回滚可以只回滚某些组件即可。ECS最重要的设计是逻辑跟数据的完全分离。即EC是纯数据,System实际上就是逻辑,由数据驱动逻辑。数据驱动逻辑是什么意思呢?很简单通过Update检测数据变化,通过事件机制来订阅数据变化,这就是所谓的数据驱动了。其它的特点例如缓存命中,在编写逻辑上来说并不太重要,现代游戏都用脚本,连脚本的性能都能容忍怎么会在乎缓存命中那点性能提升?ET在设计的时候吸收了这些想法,但是并不完全照搬,目前的设计是我经过长期的思考跟重构得来的,还是有些自己特色。 传统的ECS写逻辑作者看来存在不少缺陷,比如为了复用,数据必然要拆成非常小的颗粒,会导致组件非常非常多。但是游戏是多人合作开发的,每个人基本上只熟悉自己的模块,最后可能造成组件大量冗余。还有个问题,常见的ECS是扁平式的,Entity跟Component只有一层。组件一多,开发功能可能不知道该使用哪些Component。好比一家公司,最大的是老板,老板手下带几百个人,老板不可能认识所有的人,完成一项任务,老板没法挑出自己需要的人。合理的做法是老板手下应该有几个经理,每个经理手下应该有几个主管,每个主管管理几个工人,这样形成树状的管理结构才会容易管理。这类似ET的做法,Entity可以管理Component,Component管理Entity,甚至Component还可以挂载Component。例如:人由头,身体,手,脚组成,而头又由眼睛,耳朵,鼻子,嘴巴组成。 ```csharp Head head = human.AddComponent(); head.AddComponent(); head.AddComponent(); head.AddComponent(); head.AddComponent(); human.AddComponent(); human.AddComponent(); human.AddComponent(); ``` ET中,所有数据都是Entity,包括Entity,Entity既可以当成组件使用,也可以当做其它Entity的孩子。通用的数据放在Entity身上作为成员,不太通用的数据可以作为组件挂在Entity身上。比如物品的设计,所有物品都有配置id,数量,等级的字段,这些字段没有必要做成组件,放在Entity身上使用会更加方便。 ```csharp class Item: Entity { // 道具的配置Id public int ConfigId { get; set; } // 道具的数量 public int Count { get; set; } // 道具的等级 public int Level { get; set; } } ``` ET的这种设计数据是一种树状的结构,非常有层次,能够非常轻松的理解整个游戏的架构。顶层Game.Scene,不同模块的数据都挂载在Game.Scene上面,每个模块自身下面又可以挂载很多数据。每开发一个新功能不用思考太多,类该怎么设计,数据放在什么地方,挂载这里会不会导致冗余等等。比如我玩家需要做一个道具系统,设计一个ItemsComponent挂在Player身上即可,需要技能开发一个SpellComponent挂在Player身上。全服需要做一个活动,搞个活动组件挂在Game.Scene上面。这种设计任务分派会很简单,十分的模块化。 # 组件的一些细节 ### 1.组件的创建 组件的创建不要自己去new,应该统一使用ComponentFactory创建。ComponentFactory提供了三组方法用来创建组件Create,CreateWithParent,CreateWithId。Create是最简单的创建方式,它做了几个处理 a. 根据组件类型构造一个组件 b. 将组件加入事件系统,并且抛出一个AwakeSystem c. 是否启用对象池 CreateWithParent在Create的基础上提供了一个Parent对象,设置到Component.Parent字段上。CreateWithId是用来创建ComponentWithId或者其子类的,在Create的基础上可以自己设置一个Id, Component在创建的时候可以选择是否使用对象池。三类工厂方法都带有一个fromPool的参数,默认是true。 ### 2.组件的释放 Component都继承了一个IDisposable接口,需要注意,Component有非托管资源,删除一个Component必须调用该接口。该接口做了如下的操作 a. 抛出Destroy System b. 如果组件是使用对象池创建的,那么在这里会放回对象池 c. 从全局事件系统(EventSystem)中删除该组件,并且将InstanceId设为0 如果组件挂载Entity身上,那么Entity调用Dispose的时候会自动调用身上所有Component的Dispose方法。 ### 3.InstanceId的作用 任何Component都带有一个InstanceId字段,这个字段会在组件构造,或者组件从对象池取出的时候重新设置,这个InstanceId标识这个组件的身份。为什么需要这么一个字段呢?有以下几个原因 1. 对象池的存在,组件未必会释放,而是回到对象池中。在异步调用中,很可能这个组件已经被释放了,然后又被重新利用了起来,这样我们需要一种方式能区分之前的组件对象是否已经被释放,例如下面这段代码: ```csharp public static async ETVoid UpdateAsync(this ActorLocationSender self) { try { long instanceId = self.InstanceId; while (true) { if (self.InstanceId != instanceId) { return; } ActorTask actorTask = await self.GetAsync(); if (self.InstanceId != instanceId) { return; } if (actorTask.ActorRequest == null) { return; } await self.RunTask(actorTask); } } catch (Exception e) { Log.Error(e); } } ``` while (true)中是段异步方法,await self.GetAsync()之后很可能ActorLocationSender对象已经被释放了,甚至有可能这个对象又被其它逻辑从对象池中再次利用了起来。我们这时候可以通过InstanceId的变化来判断这个对象是否已经被释放掉。 2. InstanceId是全局唯一的,并且带有位置信息,可以通过InstanceId来找到对象的位置,将消息发给对象。这个设计将会Actor消息中利用到。这里暂时就不讲了。 ================================================ FILE: Book/3.4EventSystem.md ================================================ # EventSystem One of the most important features of ECS is the separation of data and logic, and the second is data-driven logic. What is data-driven logic? Not very well understood, let's take an example A moba game, the heroes have blood bars, which are displayed on the character's head, and also on the top left avatar UI. This time the server sends a blood deduction message. How do we handle this message? In the first way, we modify the hero's blood value in the message handling function, modify the blood bar display on the avatar, and modify the blood bar on the avatar UI at the same time. This approach obviously causes coupling between modules. In the second method, the blood value is only changed in the blood deduction message processing function, and the change of blood value throws an hpchange event, and both the avatar module and the UI module subscribe to the blood value change event and handle their own logic in the subscribed method, so that each module is responsible for its own logic without coupling. ET provides a variety of events, all of which can be subscribed multiple times: 1. AwakeSystem, which is thrown after the component factory creates a component, and is thrown only once, with parameters ```csharp Player player = ComponentFactory.Create(); // Subscribe to Player's Awake event public class PlayerAwakeSystem: AwakeSystem { public override void Awake(Player self) { } } ```` 2. StartSystem, component UpdateSystem call before throwing ```csharp // Subscribe to Player's Start event public class PlayerStartSystem: StartSystem { public override void Start(Player self) { } } ```` 3. UpdateSystem, component thrown every frame ```csharp // Subscribe to Player's Update event public class PlayerUpdateSystem: UpdateSystem { public override void Update(Player self) { } } ```` 4. DestroySystem, thrown when the component is deleted ```csharp // Subscribe to Player's Destroy event public class PlayerDestroySystem: DestroySystem { public override void Destroy(Player self) { } } Player player = ComponentFactory.Create(); // The Destroy event will be fired here player.Dispose(); ``` 5. ChangeSystem, thrown when the content of the component changes, needs to be triggered manually by the developer ```csharp // Subscribe to Player's Destroy event public class PlayerChangeSystem: ChangeSystem { public override void Change(Player self) { } } Player player = ComponentFactory.Create(); // need to trigger ChangeSystem manually Game.EventSystem.Change(player); ``` 6. DeserializeSystem, thrown after component deserialization ```csharp // Subscribe to Player's Deserialize event public class PlayerDeserializeSystem: DeserializeSystem { public override void Deserialize(Player self) { } } // Here player2 will trigger the Deserialize event Player player2 = MongoHelper.FromBson(player.ToBson()); ``` 7. LoadSystem, EventSystem thrown when loading dll, used for server-side hot update, reload dll to do some processing, such as re-register handler ```csharp // Subscribe to Player's Load event public class PlayerLoadSystem: LoadSystem { public override void Load(Player self) { } } ``` 8. normal Event, thrown by the developer himself, can take up to three parameters. Also the client hot change layer can subscribe to the mono layer Event events ```csharp int oldhp = 10; int newhp = 5; // Throw hp change event Game.EventSystem.Run("HpChange", oldhp, newhp); // UI subscribe to the hp change event [Event("HpChange")] public class HpChange_ShowUI: AEvent { public override void Run(int a, int b) { throw new NotImplementedException(); } } // The model header blood bar module also subscribes to the hp change event [Event("HpChange") public class HpChange_ModelHeadChange: AEvent { public override void Run(int a, int b) { throw new NotImplementedException(); } } ``` 9. There are many other events, such as message events. Message events are declared using MessageHandler and can take parameters to specify which server to subscribe to. ```csharp [MessageHandler(AppType.Gate)] public class C2G_LoginGateHandler : AMRpcHandler { protected override void Run(Session session, C2G_LoginGate message, Action reply) { G2C_LoginGate response = new G2C_LoginGate(); reply(reply); } } ``` More specific message events will be explained in detail when we talk about messages 10. numeric events, numeric module and then explain ...... , more events to be developed by yourself. The logic of the ET framework is driven by the various events above. ================================================ FILE: Book/3.4事件机制EventSystem.md ================================================ # 事件机制EventSystem ECS最重要的特性一是数据跟逻辑分离,二是数据驱动逻辑。什么是数据驱动逻辑呢?不太好理解,我们举个例子 一个moba游戏,英雄都有血条,血条会在人物头上显示,也会在左上方头像UI上显示。这时候服务端发来一个扣血消息。我们怎么处理这个消息?第一种方法,在消息处理函数中修改英雄的血数值,修改头像上血条显示,同时修改头像UI的血条。这种方式很明显造成了模块间的耦合。第二种方法,扣血消息处理函数中只是改变血值,血值的改变抛出一个hpchange的事件,人物头像模块跟UI模块都订阅血值改变事件,在订阅的方法中分别处理自己的逻辑,这样各个模块负责自己的逻辑,没有耦合。 ET提供了多种事件,事件都是可以多次订阅的: 1. AwakeSystem,组件工厂创建组件后抛出,只抛出一次,可以带参数 ```csharp Player player = ComponentFactory.Create(); // 订阅Player的Awake事件 public class PlayerAwakeSystem: AwakeSystem { public override void Awake(Player self) { } } ``` 2. StartSystem,组件UpdateSystem调用前抛出 ```csharp // 订阅Player的Start事件 public class PlayerStartSystem: StartSystem { public override void Start(Player self) { } } ``` 3. UpdateSystem,组件每帧抛出 ```csharp // 订阅Player的Update事件 public class PlayerUpdateSystem: UpdateSystem { public override void Update(Player self) { } } ``` 4. DestroySystem,组件删除时抛出 ```csharp // 订阅Player的Destroy事件 public class PlayerDestroySystem: DestroySystem { public override void Destroy(Player self) { } } Player player = ComponentFactory.Create(); // 这里会触发Destroy事件 player.Dispose(); ``` 5. ChangeSystem,组件内容改变时抛出,需要开发者手动触发 ```csharp // 订阅Player的Destroy事件 public class PlayerChangeSystem: ChangeSystem { public override void Change(Player self) { } } Player player = ComponentFactory.Create(); // 需要手动触发ChangeSystem Game.EventSystem.Change(player); ``` 6. DeserializeSystem,组件反序列化之后抛出 ```csharp // 订阅Player的Deserialize事件 public class PlayerDeserializeSystem: DeserializeSystem { public override void Deserialize(Player self) { } } // 这里player2会触发Deserialize事件 Player player2 = MongoHelper.FromBson(player.ToBson()); ``` 7. LoadSystem,EventSystem加载dll时抛出,用于服务端热更新,重新加载dll做一些处理,比如重新注册handler ```csharp // 订阅Player的Load事件 public class PlayerLoadSystem: LoadSystem { public override void Load(Player self) { } } ``` 8. 普通的Event,由开发者自己抛出,可以最多带三个参数。另外客户端热更层也可以订阅mono层的Event事件 ```csharp int oldhp = 10; int newhp = 5; // 抛出hp改变事件 Game.EventSystem.Run("HpChange", oldhp, newhp); // UI订阅hp改变事件 [Event("HpChange")] public class HpChange_ShowUI: AEvent { public override void Run(int a, int b) { throw new NotImplementedException(); } } // 模型头顶血条模块也订阅hp改变事件 [Event("HpChange")] public class HpChange_ModelHeadChange: AEvent { public override void Run(int a, int b) { throw new NotImplementedException(); } } ``` 9. 除此之外还有很多事件,例如消息事件。消息事件使用MessageHandler来声明,可以带参数指定哪种服务器需要订阅。 ```csharp [MessageHandler(AppType.Gate)] public class C2G_LoginGateHandler : AMRpcHandler { protected override void Run(Session session, C2G_LoginGate message, Action reply) { G2C_LoginGate response = new G2C_LoginGate(); reply(response); } } ``` 更具体的消息事件等到讲消息的时候再细细讲解了 10. 数值事件,数值模块再讲解 ......, 更多的事件由自己去开发。 ET框架的逻辑就是由以上各种事件来驱动的。 ================================================ FILE: Book/4.1Component-based design.md ================================================ # Component-based design In terms of code reuse and organization of data, object-oriented may be the first response. Object-oriented three features inheritance, encapsulation, polymorphism, to a certain extent can solve a lot of code reuse, data reuse problems. But object-oriented is not a panacea, it also has great flaws: # # 1. ## 1. data structure coupling is very strong Once a field is added or removed from a parent class, it may have to affect all subclasses and affect all subclass-related logic. This seems very inflexible, in a complex set of inheritance system, to change the fields in the parent class will become more and more troublesome, let's say ABC is a subclass of D, one day found the need to add a data that AB has, but C does not, then this data is certainly not good to put into the parent class, only AB abstracted out of a parent class E, E inherited from D, AB common fields added to E, once the inheritance structure Once the inheritance structure has changed, the interface may also need to change, let's say there is an interface incoming parameter type is E, when AB no longer needs the common field, then the inheritance relationship needs to be adjusted so that AB inherits D again, then the interface incoming parameter type needs to be changed to D, where the logic code is likely to be adjusted. What's worse is that the game logic changes very complicated and very often, maybe today a field is added, tomorrow it is deleted, if every time to adjust the inheritance structure, it is a nightmare. Inheritance structure feels very powerless in the face of frequent data structure adjustment. ## 2. Difficult to hot-plug Inheritance structure can't add or delete fields at runtime, for example, Player usually walks, and then rides after using mount. The problem is that the information about the mount would need to be hanging on top of the Player object all the time. This makes it inflexible. Why do I need to have the horse data in memory when I'm not riding? The interface has the same problem, a class implements an interface, then the interface will always stick to the class, you want to get rid of her can not, or horse riding for example, the player player can ride, then may inherit a riding interface, the problem is, when I the Player from the mount, the player player still has the riding interface, there is no way to There is no way to dynamically delete this interface! The example may not be quite right, but the reasoning should be clear. The use of object-oriented may lead to disastrous consequences, game development in the new and old, there are good skills, there are poor skills. People like to be lazy, when you find the trouble to adjust the inheritance relationship, it is possible to add a field in AB to save trouble directly into the parent class D. The result is that C somehow has an extra field that is useless. The key can not be found, and finally lead to the parent class D is getting bigger and bigger, in the end, it may simply not ABC, directly let all the objects into D, convenient well! Yes, many games are doing this, the development of the end simply do not care about the inheritance relationship, because you want to manage can not manage. ## 3. method and data coupling Traditional object-oriented are class with methods, and especially advocate virtual function polymorphism. Methods and data put together brings a lot of coupling problems. In order to solve these coupling, we came up with a large number of design patterns, such as dependency interfaces, dependency transposition. To be honest, this is pants down, in order to understand the coupling, make the class into an interface, and then inherit from the interface, is this not called dependency? These practices lead to code that is full of interfaces and extremely difficult to read. There is no standard for writing up code, and the code written by experts and rookies is completely different. Most coders are logic boys, who have time to think about how to design this class every day ah? As the logic becomes more and more complex class inside the method will become increasingly large, the terrible thing is that this is the method of the class, extremely difficult to refactor, many projects can see the class inside the existence of tens of thousands of lines of code of virtual functions. Gosh! Object-oriented in the face of complex game logic is very powerless, so many game developers have gone backwards, using process-oriented development game, process-oriented, simple and brutal, do not consider complex inheritance, do not consider abstraction, do not consider polymorphism, is the development of the session of freestyle, roll up your sleeves on the jerk, but at the same time, the reusability of the code logic, data reusability is also greatly reduced. Process-oriented is also not a good game development model. Component pattern is a good solution to object-oriented and process-oriented defects, in the game client is very widely used, Unity3d, Unreal 4, and so on are using the component pattern. The characteristics of the component pattern. 1. Highly modular, a component is a piece of data plus a paragraph of logic 2. Components can be hot-pluggable, you need to add, do not need to remove 3. Very few dependencies between types, any type of adding or removing components will not affect other types. But at present only a very small number of server-side design using components, Watchtower server-side should be the use of component design, Watchtower developers called ECS architecture, in fact, is a variant of the component model, E is Entity, C is Component, S is System, in fact, is the component Component logic and data stripped, the logic part called System, the topic is far away, or back to the ET framework to put. ET framework uses the design of components. Everything is Entity and Component, any class inherited from Entity can be mounted components, such as the player class. ```C# public sealed class Player : Entity { public string Account { get; private set; } public long UnitId { get; set; } public void Awake(string account) { this.Account = account; } public override void Dispose() { if (this.Id == 0) { return; } base.Dispose(); } } ``` Mount a MoveComponent to the player object so that the player can move, a Backpack component to the player so that the player can manage items, a Skill component to the player so that the player can cast skills, and a Buff component to manage buffs. ```C# player.AddComponent(); player.AddComponent(); player.AddComponent(); AddComponent(); player.AddComponent(); ``` Components are highly reusable, for example, an NPC, he can also move, just put MoveComponent on the NPC, some NPCs can also cast skills, then put SpellComponent on it, NPCs do not need a backpack, then there is no need to hang ItemsComponent ET framework modules are all made into the form of components, a process is also made of different components stitched together. Let's say Loginserver needs to connect externally and also needs to connect with the server internally, then login server hooks up ```C# // intranet network component NetInnerComponent, which handles the connection to the intranet Game.Scene.AddComponent(innerConfig.Host, innerConfig.Port); // NetOuterComponent, an extranet component, handles the connection to the client Game.Scene.AddComponent(outerConfig.Host, outerConfig.Port); ``` For example, battle server does not need to connect to the external network (external network messages are forwarded by gateserver), so it is natural to just mount an internal network component. Similar to Unity3d components, ET framework also provides component events, such as Awake, Start, Update, etc.. To add these events to a Component or Entity, you must write a helper class. For example, if the NetInnerComponent component needs Awake and Update methods, then add a class like this. ```C# [ObjectEvent] public class NetInnerComponentEvent : ObjectEvent, IAwake, IUpdate { public void Awake() { this.Get().Awake(); } public void Update() { this.Get().Update(); } } ```` In this way, NetInnerComponent calls its Awake method after AddComponent and calls the Update method every frame. ET does not use reflection like Unity to implement this kind of functionality, because reflection performance is poor, and the advantage of this implementation is that the class can be placed in the hot update dll, so that the component's Awake Start, Update method and other methods can be placed in the hot update layer. Entity and Component will be made into a class without methods, methods are put into the hot update layer to facilitate the hot update to fix logic bugs. The biggest advantage of component-based development is that whether rookie or expert, the development of a function can quickly know how to organize data how to organize the logic. Object-oriented can be completely abandoned. Object-oriented development using the most headache is that I should inherit which class it? Before doing the most horrible is Unreal three, Unreal three inheritance structure is very multi-layer, completely do not know where they need to start inheritance. In the end, it could lead to a very small function that inherits a very large class, which is common in Unreal 3 development. So Unreal 4 switched to the component pattern. The module isolation of the component pattern is very good, the technical rookie a component written very poorly, will not affect the other modules, the big deal is to rewrite the component on the good. ET's component design innovation, method and data separation, completely decoupled, no brainstorming how to uncouple, write static methods at will, there is no coupling, even the code written by rookies is easy to refactor. It is because ET uses the detachable component model, ET can load all server components to the same process, then this one process can be used as a set of distributed servers. From then on it is possible to debug distributed servers with vs. Because of this, the usual development only use a process, when the release of the release into multiple processes on the line. Honestly, not to brag, this is a great invention, this invention solves a big problem in the development of distributed game servers, greatly improving the efficiency of development. ================================================ FILE: Book/4.1组件式设计.md ================================================ # 组件式设计 在代码复用和组织数据方面,面向对象可能是大家第一反应。面向对象三大特性继承,封装,多态,在一定程度上能解决不少代码复用,数据复用的问题。不过面向对象不是万能的,它也有极大的缺陷: ## 1. 数据结构耦合性极强 一旦父类中增加或删除某个字段,可能要影响到所有子类,影响到所有子类相关的逻辑。这显得非常不灵活,在一套复杂的继承体系中,往父类中改变字段会变得越来越麻烦,比方说ABC是D的子类,某天发现需要增加一个AB都有的数据,但是C没有,那么这个数据肯定不好放到父类中,只能将AB抽象出来一个父类E,E继承于D,AB共有的字段加到E中,一旦继承结构发生了变化,可能接口也要改变,比方说之前有个接口传入参数类型是E,当AB不再需要共用的那个字段,那么需要调整继承关系,让AB重新继承D,那么这个接口的传入参数类型需要改成D,其中的逻辑代码很可能也要发生调整。更可怕的是游戏逻辑变化非常复杂,非常频繁,可能今天加了个字段,明天又删掉了,假如每次都要去调整继承结构,这简直就是噩梦。继承结构面对频繁的数据结构调整感觉很无力。 ## 2. 难以热插拔 继承结构无法运行时增加删除字段,比如玩家Player平常是走路,使用坐骑后就骑马。问题是坐骑的相关信息就需要一直挂在Player对象上面。这就显得很不灵活,我不骑马的时候内存中为啥要有马的数据?接口也有同样的问题,一个类实现了一个接口,那么这个接口就永远粘在了这个类身上,你想甩掉她都不行,还是以骑马为例,玩家Player可以进行骑行,那么可能继承一个骑行的接口,问题是,当我这个Player从坐骑上下来时,玩家Player身上还是有骑行的接口,根本没法动态删掉这个接口!可能例子举得不是很对,但是道理表述的应该很清楚了。 使用面向对象可能导致灾难性后果,游戏开发中有新人有老人,有技术好的,有技术差的。人都是喜欢偷懒的,当你发现调整继承关系麻烦的时候,有可能AB中增加一个字段为了省事直接就放到父类D中去了。导致C莫名奇妙的多了一个无用的字段。关键还没法发现,最后导致父类D越来越大,到最后有可能干脆就不用ABC了,直接让所有对象都变成D,方便嘛!是的,很多游戏就是这么干的,开发到最后根本就不管继承关系了,因为想管也管不了了。 ## 3. 方法与数据耦合 传统面向对象都是class中带有方法,并且特别提倡虚函数多态。方法跟数据放在一起带来了特别多耦合的问题。为了解决这些耦合,大家想出了大量的设计模式,比如依赖接口,依赖转置。说实话,这就是脱裤子放屁,为了解耦合,把类做成接口,然后继承接口,难道这就不叫依赖了?这些做法导致,代码中到处是接口,代码阅读极其困难。写起代码来也没有个标准,高手跟菜鸡写出来的代码完全是两回事。大部分码农都是逻辑仔,谁有时间天天想这个类要怎么设计啊?随着逻辑越来越复杂类里面的方法将越来越庞大,可怕的是,这是这个类的方法,极其难以重构,很多项目中能看到类里面存在上万行代码的虚函数。天哪! 面向对象在面对复杂的游戏逻辑时很无力,所以很多游戏开发者又倒退了回去,使用面向过程进行开发游戏,面向过程,简单粗暴,不考虑复杂的继承,不考虑抽象,不考虑多态,是开发届的freestyle,挽起袖子就开撸,但同时,代码逻辑的复用性,数据的复用性也大大降低。面向过程也不是一种好的游戏开发模式。 组件模式很好的解决了面向对象以及面向过程的种种缺陷,在游戏客户端中使用非常广泛,Unity3d,虚幻4,等等都使用了组件模式。组件模式的特点: 1.高度模块化,一个组件就是一份数据加一段逻辑 2.组件可热插拔,需要就加上,不需要就删除 3.类型之间依赖极少,任何类型增加或删除组件不会影响到其它类型。 但是目前只有极少有服务端使用了组件的设计,守望先锋服务端应该是使用了组件的设计,守望先锋的开发人员称之为ECS架构,其实就是组件模式的一个变种,E就是Entity,C就是Component,S是System,其实就是将组件Component的逻辑与数据剥离,逻辑部分叫System,话题扯远了,还是回到ET框架来把。 ET框架使用了组件的设计。一切都是Entity和Component,任何类继承于Entity都可以挂载组件,例如玩家类: ```C# public sealed class Player : Entity { public string Account { get; private set; } public long UnitId { get; set; } public void Awake(string account) { this.Account = account; } public override void Dispose() { if (this.Id == 0) { return; } base.Dispose(); } } ``` 给玩家对象挂载个移动组件MoveComponent,这样玩家就可以移动了,给玩家挂上一个背包组件,玩家就可以管理物品了,给玩家挂上技能组件,那么玩家就可以施放技能了,加上Buff组件就可以管理buff了。 ```C# player.AddComponent(); player.AddComponent(); player.AddComponent(); player.AddComponent(); ``` 组件是高度可以复用的,比如一个NPC,他也可以移动,给NPC也挂上MoveComponent就行了,有的NPC也可以施放技能,那么给它挂上SpellComponent,NPC不需要背包,那么就不用挂ItemsComponent了 ET框架模块全部做成了组件的形式,一个进程也是由不同的组件拼接而成。比方说Loginserver需要对外连接也需要与服务器内部进行连接,那么login server挂上 ```C# // 内网网络组件NetInnerComponent,处理对内网连接 Game.Scene.AddComponent(innerConfig.Host, innerConfig.Port); // 外网网络组件NetOuterComponent,处理与客户端连接 Game.Scene.AddComponent(outerConfig.Host, outerConfig.Port); ``` 比如battle server就不需要对外网连接(外网消息由gateserver转发),那么很自然的只需要挂载一个内网组件即可。 类似Unity3d的组件,ET框架也提供了组件事件,例如Awake,Start,Update等。要给一个Component或者Entity加上这些事件,必须写一个辅助类。比如NetInnerComponent组件需要Awake跟Update方法,那么添加一个这样的类即可: ```C# [ObjectEvent] public class NetInnerComponentEvent : ObjectEvent, IAwake, IUpdate { public void Awake() { this.Get().Awake(); } public void Update() { this.Get().Update(); } } ``` 这样,NetInnerComponent在AddComponent之后会调用其Awake方法,并且每帧调用Update方法。 ET没有像Unity使用反射去实现这种功能,因为反射性能比较差,而且这样实现的好处是这个类可以放到热更dll中,这样组件的Awake Start,Update方法以及其它方法都可以放到热更层中。将Entity和Component做成没有方法的类,方法都放到热更层,方便热更修复逻辑bug。 组件式开发最大的好处就是不管菜鸟还是高手,开发一个功能都能很快的知道怎么组织数据怎么组织逻辑。可以完全放弃面向对象。使用面向对象开发最头疼的就是我该继承哪个类呢?之前做过最恐怖的就是虚幻三,虚幻三的继承结构非常多层,完全不知道自己需要从哪里开始继承。最后可能导致一个非常小的功能,继承了一个及其巨大的类,这在虚幻三开发中屡见不鲜。所以虚幻4改用了组件模式。组件模式的模块隔离性非常好,技术菜鸟某个组件写得非常差,也不会影响到其它模块,大不了重写这个组件就好了。 ET的组件设计有所创新,方法跟数据分离,完全解除耦合,不用绞尽脑汁去想怎么解除耦合,随意写静态方法即可,根本不存在耦合,即使是菜鸟写的代码也很容易重构。 正是因为ET使用了可拆卸的组件模式,ET可以将所有服务器组件都装到同一个进程上,那么这一个进程就可以当作一组分布式服务器使用。从此用vs调试分布式服务器成为了可能。正因为这样,平常开发只使用一个进程,发布的时候发布成多个进程就行了。说实在的,不是吹牛,这是一个伟大的发明,这一发明解决了分布式游戏服务器开发中的大大大难题,极大的提高了开发效率。 ================================================ FILE: Book/5.4Actor Model.md ================================================ # Actor model ### Actor introduction Before discussing the Actor model, we should discuss the architecture of ET. There are two architectures for game servers in order to use multi-core, single-threaded multi-process and single-process multi-threaded architecture. ET uses single-threaded multi-process architecture, while the traditional Actor model is generally single-process multi-threaded architecture, which is a major difference. The advantages and disadvantages are as follows. 1. the logic needs to be single-threaded this is the same, erlang process logic is single-threaded, skynet lua virtual machine is also single-threaded. et in a process is actually equivalent to an erlang process, a skynet lua virtual machine. 2. the use of single-threaded multi-process does not need to write their own set of profiler tools, you can use a lot of ready-made profiler tools, such as view memory, cpu occupation directly with top command, this point erlang and skynet need to get another set of tools. 3. multi-process single-threaded architecture also has a benefit, a single physical machine and multiple physical machines is no difference, single process multi-threaded also need to consider the processing of multiple physical machines. 4. multi-process single-threaded architecture is a bit of a drawback is that messages need to be serialized and deserialized across processes, taking up a bit of resources. In addition, sending network messages will have a few milliseconds delay. Generally these effects can be ignored. The original Actor model is used for single-process multi-threaded architecture, there is a reason for this, because multi-threaded architecture developers can easily access shared variables at will, let's say a variable a, thread 1 can access, thread 2 can also access, so that both threads need to add locks when accessing the variable a, shared variables more locks everywhere, will become unmaintainable, the framework must not appear The framework must not have a situation where there are threads sharing variables everywhere. In order to ensure that the multi-threaded architecture does not go wrong, it is necessary to provide a development model that ensures easy and safe multi-threaded development. erlang's concurrency mechanism is the actor model. erlang virtual machine uses multiple threads to take advantage of multiple cores. erlang has designed a mechanism that designs its own processes on top of the virtual machine. At its simplest, each erlang process manages its own variables, and the logic of each erlang process runs on a single thread. The logic between the erlang process and the process is completely isolated, so that there are no two threads accessing the same variable and there is no multithreaded competition. The next question arises, since each erlang process has its own data and the logic is completely isolated, how should the two erlang processes communicate with each other? This is where the Actor model comes in. erlang has designed a messaging mechanism: one process can send messages to other processes, and erlang processes communicate with each other through messages. Isn't this the same message queue used by operating systems for inter-process communication? Yes, in fact, it is similar. erlang inside the process id to get the process can send messages to this process. If the message is only sent to the process, it is still a bit inconvenient. For example, if you take an erlang process as a moba team process, and there are 10 players in the battle process, if you use erlang's actor message, the message can only be sent to the battle process, but often the message needs to be sent to a player, then erlang needs to distribute the message to the specific player again according to the player id in the message, so it actually goes around one more time. This is actually an extra detour. ### ET's Actor According to the characteristics of its own architecture, ET does not completely copy the Actor model of erlang, but provides the Entity object-level Actor model. In ET, an Actor is an Entity object, and a MailboxComponent component attached to an Entity is an Actor. You only need to know the Entity's InstanceId to send messages to the Entity. In fact, erlang's Actor model is a special case of ET, such as giving the ET server Game.Scene as an Actor, so that it can become a process-level Actor. It only needs to know the InstanceId (ET) or the Pid (erlang) of the process to send it to the other party. | Language | ET | Erlang | Skynet | | | ET | Erlang | Skynet | -- | :--: | :--: | :--: | | Architecture | Single-Threaded Multi-Process | Single-Process Multi-Threaded | Single-Process Multi-Threaded | | Actor | Entity | erlang process | lua virtual machine | | ActorId | Entity.InstanceId | erlang processId | service address | ### Use of ET's Actor For a normal Actor, we can refer to the Gate Session. map has a Unit, and the Unit holds the gate session corresponding to the player. thus, if a message in map needs to be sent to the client, it only needs to send the message to the gate session, and the gate session forwards it to the client when it receives the message. The map process sending messages to the gate session is a typical actor model. It doesn't need to know the location of the gate session, it just needs to know its InstanceId. messageHelper.cs gets an ActorMessageSender from GateSessionActorId and sends it. ```csharp // Get an ActorSenderComponent from Game.Scene, then get an ActorMessageSender by InstanceId ActorSenderComponent actorSenderComponent = Game.Scene.GetComponent(); ActorMessageSender actorMessageSender = actorSenderComponent.Get(unitGateComponent.GateSessionActorId); // send actorMessageSender.Send(message); // rpc var response = actorMessageSender.Call(message); ``` The question is how do you know the InstanceId of the gate session in map? This is where you need to find a way to pass it on, for example in ET, when the player is logging in to gate, the gate session hooks up a mailbox MailBoxComponent, C2G_LoginGateHandler.cs ```csharp session.AddComponent(MailboxType.GateSession); ``` The InstanceId of this gate session is brought into the map when the player logs into the map process, in C2G_EnterMapHandler.cs ```csharp M2G_CreateUnit createUnit = (M2G_CreateUnit)await mapSession.Call(new G2M_CreateUnit() { PlayerId = player.Id, GateSessionId = session. InstanceId }); ``` ### Handling of Actor messages First, the message arrives at the MailboxComponent, which has a type, and different types of mailboxes can do different processing. Currently, there are two types of mailboxes, GateSession and MessageDispatcher; GateSession mailboxes will immediately forward messages to the client when they are received, and MessageDispatcher types will again distribute the Actor messages to the specific Handler for processing. The default MailboxComponent type is MessageDispatcher. customizing a mailbox type is also very simple, inherit the IMailboxHandler interface and add the MailboxHandler tag. So why do we need to add such a feature? This feature does not exist in other actor models, and messages are generally received and distributed. The reason is that GateSession is not designed for distribution, so I added the mailbox type here. messageDispatcher has two ways of handling messages, one is to handle the messages sent by the other party, and the other is rpc messages ```csharp // To handle Send messages, you need to inherit the AMActorHandler abstract class. The first generic parameter of the abstract class is the type of the Actor, and the second parameter is the type of the message [ActorMessageHandler(AppType.Map)] public class Actor_TestHandler : AMActorHandler { protected override ETTask Run(Unit unit, Actor_Test message) { Log.Debug(message.Info); } } // To handle Rpc messages, you need to inherit the AMActorRpcHandler abstract class, the first generic parameter of the abstract class is the type of the Actor, the second parameter is the type of the message, and the third parameter is the type of the returned message [ActorMessageHandler(AppType.Map)] public class Actor_TransferHandler : AMActorRpcHandler { protected override async ETTask Run(Unit unit, Actor_TransferRequest message, Action reply) { Actor_TransferResponse response = new Actor_TransferResponse(); try { reply(response); } catch (Exception e) { ReplyError(response, e, reply); } } } ``` We should note that Actor messages have the potential to deadlock, such as A call to B, B call to C, and C call to A. Because MailboxComponent is essentially a message queue, it opens a concurrent process that will process one message at a time, returning ETTask to indicate that the message processing class will block MailboxComponent queue of other messages. So if there is a deadlock, we don't want a message processing to block the rest of the MailboxComponent messages, we can just open a new thread in the message processing class to handle it. For example: ```csharp [ActorMessageHandler(AppType.Map)] public class Actor_TestHandler : AMActorHandler { protected override ETTask Run(Unit unit, Actor_Test message) { RunAsync(unit, message).Coroutine(); } public ETVoid RunAsync(Unit unit, Actor_Test message) { Log.Debug(message.Info); } } ``` For related information, you can Google the Actor deadlock problem. ================================================ FILE: Book/5.4Actor模型.md ================================================ # Actor模型 ### Actor介绍 在讨论Actor模型之前先要讨论下ET的架构,游戏服务器为了利用多核一般有两种架构,单线程多进程跟单进程多线程架构。两种架构本质上其实区别不大,因为游戏逻辑开发都需要用单线程,即使是单进程多线程架构,也要用一定的方法保证单线程开发逻辑。ET采用的是单线程多进程的架构,而传统Actor模型一般是单进程多线程的架构,这点是比较大的区别,不能说谁更好,只能说各有优势。优劣如下: 1. 逻辑需要单线程这点都是一样的,erlang进程逻辑是单线程的,skynet lua虚拟机也是单线程的。ET中一个进程其实相当于一个erlang进程,一个skynet lua虚拟机。 2. 采用单线程多进程不需要自己再写一套profiler工具,可以利用很多现成的profiler工具,例如查看内存,cpu占用直接用top命令,这点erlang跟skynet都需要自己另外搞一套工具。 3. 多进程单线程架构还有个好处,单台物理机跟多台物理机是没有区别的,单进程多线程还需要考虑多台物理机的处理。 4. 多进程单线程架构一点缺陷是消息跨进程需要进行序列化反序列化,占用一点资源。另外发送网络消息会有几毫秒延时。一般这些影响可以忽略。 最开始Actor模型是给单进程多线程架构使用的,这是有原因的,因为多线程架构开发者很容易随意的访问共享变量,比方说一个变量a, 线程1能访问,线程2也能访问,这样两个线程在访问变量a的时候都需要加锁,共享变量多了之后锁到处都是,会变得无法维护,框架肯定不能出现到处是线程共享变量的情况。为了保证多线程架构不出问题,必须提供一种开发模型保证多线程开发简单又安全。erlang语言的并发机制就是actor模型。erlang虚拟机使用多线程来利用多核。erlang设计了一种机制,它在虚拟机之上设计了自己的进程。最简单的,每个erlang进程都管理自己的变量,每个erlang进程的逻辑都跑在一个线程上,erlang进程跟进程之间逻辑完全隔离,这样就不存在两个线程访问同一变量的情况了也就不存在多线程竞争的问题。接下来问题又出现了,既然每个erlang进程都有自己的数据,逻辑完全是隔离的,两个erlang进程之间应该怎么进行通信呢?这时Actor模型就登场了。erlang设计了一种消息机制:一个进程可以向其它进程发送消息,erlang进程之间通过消息来进行通信,看到这会不会感觉很熟悉?这不就是操作系统进程间通信用的消息队列吗?没错,其实是类似的。erlang里面拿到进程的id就能给这个进程发送消息。 如果消息只发给进程其实还是有点不方便。比如拿一个erlang进程做moba战队进程,战斗进程中有10个玩家,如果使用erlang的actor消息,消息只能发送给战斗进程,但是很多时候消息是需要发送给一个玩家的,这时erlang需要根据消息中的玩家Id,把消息再次分发给具体的玩家,这样其实多绕了一圈。 ### ET的Actor ET根据自己架构得特点,没有完全照搬erlang的Actor模型,而是提供了Entity对象级别的Actor模型。这点跟erlang甚至传统的Actor机制不一样。ET中,Actor是Entity对象,Entity挂上一个MailboxComponent组件就是一个Actor了。只需要知道Entity的InstanceId就可以发消息给这个Entity了。其实erlang的Actor模型不过是ET中的一种特例,比如给ET服务端Game.Scene当做一个Actor,这样就可以变成进程级别的Actor。Actor本质就是一种消息机制,这种消息机制不用关心位置,只需要知道对方的InstanceId(ET)或者进程的Pid(erlang)就能发给对方。 | 语言 | ET | Erlang | Skynet | | -- | :--: | :--: | :--: | | 架构 | 单线程多进程 | 单进程多线程 | 单进程多线程 | | Actor | Entity | erlang进程 | lua虚拟机 | | ActorId | Entity.InstanceId | erlang进程Id | 服务地址 | ### ET的Actor的使用 普通的Actor,我们可以参照Gate Session。map中一个Unit,Unit身上保存了这个玩家对应的gate session。这样,map中的消息如果需要发给客户端,只需要把消息发送给gate session,gate session在收到消息的时候转发给客户端即可。map进程发送消息给gate session就是典型的actor模型。它不需要知道gate session的位置,只需要知道它的InstanceId即可。MessageHelper.cs中,通过GateSessionActorId获取一个ActorMessageSender,然后发送。 ```csharp // 从Game.Scene上获取ActorSenderComponent,然后通过InstanceId获取ActorMessageSender ActorSenderComponent actorSenderComponent = Game.Scene.GetComponent(); ActorMessageSender actorMessageSender = actorSenderComponent.Get(unitGateComponent.GateSessionActorId); // send actorMessageSender.Send(message); // rpc var response = actorMessageSender.Call(message); ``` 问题是map中怎么才能知道gate session的InstanceId呢?这就是你需要想方设法传过去了,比如ET中,玩家在登录gate的时候,gate session挂上一个信箱MailBoxComponent,C2G_LoginGateHandler.cs中 ```csharp session.AddComponent(MailboxType.GateSession); ``` 玩家登录map进程的时候会把这个gate session的InstanceId带进map中去,C2G_EnterMapHandler.cs中 ```csharp M2G_CreateUnit createUnit = (M2G_CreateUnit)await mapSession.Call(new G2M_CreateUnit() { PlayerId = player.Id, GateSessionId = session.InstanceId }); ``` ### Actor消息的处理 首先,消息到达MailboxComponent,MailboxComponent是有类型的,不同的类型邮箱可以做不同的处理。目前有两种邮箱类型GateSession跟MessageDispatcher。GateSession邮箱在收到消息的时候会立即转发给客户端,MessageDispatcher类型会再次对Actor消息进行分发到具体的Handler处理,默认的MailboxComponent类型是MessageDispatcher。自定义一个邮箱类型也很简单,继承IMailboxHandler接口,加上MailboxHandler标签即可。那么为什么需要加这么个功能呢,在其它的actor模型中是不存在这个特点的,一般是收到消息就进行分发处理了。原因是GateSession的设计,并不需要进行分发处理,因此我在这里加上了邮箱类型这种设计。MessageDispatcher的处理方式有两种一种是处理对方Send过来的消息,一种是rpc消息 ```csharp // 处理Send的消息, 需要继承AMActorHandler抽象类,抽象类第一个泛型参数是Actor的类型,第二个参数是消息的类型 [ActorMessageHandler(AppType.Map)] public class Actor_TestHandler : AMActorHandler { protected override ETTask Run(Unit unit, Actor_Test message) { Log.Debug(message.Info); } } // 处理Rpc消息, 需要继承AMActorRpcHandler抽象类,抽象类第一个泛型参数是Actor的类型,第二个参数是消息的类型,第三个参数是返回消息的类型 [ActorMessageHandler(AppType.Map)] public class Actor_TransferHandler : AMActorRpcHandler { protected override async ETTask Run(Unit unit, Actor_TransferRequest message, Action reply) { Actor_TransferResponse response = new Actor_TransferResponse(); try { reply(response); } catch (Exception e) { ReplyError(response, e, reply); } } } ``` 我们需要注意一下,Actor消息有死锁的可能,比如A call消息给B,B call给C,C call给A。因为MailboxComponent本质上是一个消息队列,它开启了一个协程会一个一个消息处理,返回ETTask表示这个消息处理类会阻塞MailboxComponent队列的其它消息。所以如果出现死锁,我们就不希望某个消息处理阻塞掉MailboxComponent其它消息的处理,我们可以在消息处理类里面新开一个协程来处理就行了。例如: ```csharp [ActorMessageHandler(AppType.Map)] public class Actor_TestHandler : AMActorHandler { protected override ETTask Run(Unit unit, Actor_Test message) { RunAsync(unit, message).Coroutine(); } public ETVoid RunAsync(Unit unit, Actor_Test message) { Log.Debug(message.Info); } } ``` 相关资料可以谷歌一下Actor死锁的问题。 ================================================ FILE: Book/5.5Actor Location-EN.md ================================================ # Actor Location ### Actor Location Actor model only needs to know each other's InstanceId to send messages, which is very convenient, but sometimes we may not know each other's InstanceId, or an Actor's InstanceId will change. ET provides a mechanism to send messages to such objects, called Actor Location The mechanism is called Actor Location. The principle is relatively simple. 1. because the InstanceId is changing, the object's Entity.Id is unchanged, so we can first think of using Entity.Id to send actor messages 2. provide a location process (Location Server), the Actor object can store its Entity.Id and InstanceId as kv to the location process. Before sending the Actor message, go to the location process to look up the InstanceId of the Actor object before sending the actor message. 3. 3. When an Actor object is created in a process or migrated to a new process, it needs to register its Id and InstanceId to the Location Server. 4. 4. because the Actor object can be migrated, the message may be sent to the Actor has been migrated to other processes, so sending the Actor Location message needs to provide a reliable mechanism 5. ActorLocationSender provides two methods, Send and Call, Send a message also requires the recipient to return a message, and only when the return message is received will the next message be sent. 6. 6. If the Actor object is migrated away, it will return the error that the Actor does not exist, the sender will wait for 1 second after receiving this error, then go back to get the InstanceId of the Actor and resend it, currently it will try 5 times, after 5 times, it will throw an exception and report the error 7. ActorLocationSender will not query the Location Server every time it sends a message, because object migration is relatively rare after all, only the first time to query, then cache the InstanceId, and re-query after the failure to send. 8. actor object in the migration process, it is possible that other processes send over messages, when an error will occur, so the location server provides a Lock mechanism. Before the object is transmitted, the information in the process is deleted, and then a lock is added to the location server, and once the lock is on, other requests for the key will be queued. 9. before transmission because the other party deleted the actor of the process, so other processes will fail to send, then they will retry. When retrying, they will re-request the location server, which will be found to be locked, so they will keep waiting. 10. When the transmission is completed, the lock on the location server is unlocked, and the new address is updated, and then other location requests are responded to. Other requests sent to this actor continue. Note that the Actor model is purely a server-side message communication mechanism, which has nothing to do with the client. We can use the server-side actor model for forwarding, so some client-side messages also inherit the actor interface. What happens if we don't use the actor interface on the client side? For example, the message Frame_ClickMap ```protobuf message Frame_ClickMap // IActorLocationMessage { int64 ActorId = 93; int64 Id = 94; float X = 1; float Y = 2; float Z = 3; } ``` We may not need the field ActorId, the message is sent to Gate, gate sees that it is a Frame_ClickMap message, it needs to be forwarded to the Unit on the Map, forwarding is still good, gate can get the location of the unit corresponding to the map from the session, and then forward it, the problem comes, Frame_ ClickMap message to the map, how does the map know which object the message needs to be given to? There are several designs at this point. 1. bring the Id of the unit in the underlying protocol of forwarding, which requires more complex underlying protocol support. 2. use a message to Frame_ClickMap message wrapping, wrapping the message with the Id of the Unit, wrapping with the message means greater consumption, increasing GC. Personally, I feel that these two are very poor, not good, and even if distributed to the unit object processing, how to solve the problem of message re-entry it? unit object still needs to hang a message processing queue, and then receive the message thrown into the queue. Isn't this a duplication of the actor model? The current ET message sent to unit in the client did a design, the message into an actor message, gate received found to be an actor message, sent directly to the corresponding actor, the solution can be said to be beautiful. In fact, the client is still using session.send and call to send messages, send the message does not know that the message is an actor message, only to the gate, the gate judgment, refer to OuterMessageDispatcher.cs ### Actor Location message processing ActorLocation messages are sent ```csharp // Get the ActorLocationSenderComponent from Game.Scene, then get the ActorLocationSender via Entity.Id ActorLocationSender actorLocationSender = Game.Scene.GetComponent().Get(unitId); // Send the message through the ActorLocationSender actorLocationSender.Send(actorLocationMessage); // send the Rpc message IResponse response = await actorLocationSender.Call(actorLocationRequest); ``` ActorLocation message processing is almost the same as Actor messages, the difference is that the two abstract classes inherited are different, note that the abstract class of actorlocation has an additional Location ```csharp // The first generic parameter of the abstract class is the type of the Actor, and the second parameter is the type of the message. [ActorMessageHandler(AppType.Map)] public class Frame_ClickMapHandler : AMActorLocationHandler { protected override ETTask Run(Unit unit, Frame_ClickMap message) { Vector3 target = new Vector3(message.X, message.Y, message.Z); unit.GetComponent().MoveTo(target).Coroutine(); } } // To handle Rpc messages, you need to inherit the AMActorRpcHandler abstract class. The first generic parameter of the abstract class is the type of the Actor, the second parameter is the type of the message, and the third parameter is the type of the returned message [ActorMessageHandler(AppType.Map)] public class C2M_TestActorRequestHandler : AMActorLocationRpcHandler { protected override async ETTask Run(Unit unit, C2M_TestActorRequest message, Action reply) { reply(new M2C_TestActorResponse(){Info = "actor rpc response"}); await ETTask.CompletedTask; } } ``` ### ET's actor and actor location analogy There are many cities (processes) in China, and many people (entity objects) living in the cities, each with an ID number (Entity.Id). A person needs to apply for a residence permit in each city and is assigned a unique residence permit number (InstanceId). The format of the residence permit number is 2 bytes city number + 4 bytes time + 2 bytes increment. The ID number never changes, but the residence permit number changes every time you go to a city. Now there is a China Post (actor). Suppose Xiaoming wants to send a letter to his girlfriend Xiaohong 1. Xiaohong must mount a mailbox (MailboxComponent) himself in order to receive the letter, and Xiaohong will process the message when he receives it. Note that processing is done here one by one. It is possible that Hong will receive letters from many people at the same time. But she must read one letter at a time. Let's say Xiaoming and Xiaobao both send letters to Xiaohong, and Xiaohong receives Xiaoming's letter first, and then Xiaobao's letter. Xiaohong reads Xiaoming's letter first, and Xiaoming's letter asks Xiaohong to make a phone call to her grandmother (to produce a concordance) and then write back to herself, noting that Xiaohong also cannot read the next letter during this period, and must finish the phone call before she can read Xiaobao's letter. Of course Xiao Hong himself can choose to start reading Xiao Bao's letter without finishing the process, by opening a new concatenation for Xiao Ming's letter. 2. Suppose Xiaoming knows Xiaohong's residence permit number, then the post (actor) can find the city (process) where Xiaohong lives according to the two docks of the residence permit number, and then find Xiaohong and deliver the message to Xiaohong's mailbox (MailboxComponent) according to Xiaohong's residence permit number. This is the simplest native actor model 3. ET also supports a set of actor location mechanism. Suppose Xiao Ming doesn't know Xiao Hong's residence permit number, but he knows Xiao Hong's ID number, what should he do? The postal service has developed a set of advanced post (actor location) to think of a way, if a person often moves, it still wants to receive letters, then he must report his residence permit and ID card to the central government (location server) when he goes to a new city, so that the advanced post can send mail through the ID card number. The method is to go to the central government to get Hong's residence permit number, and then use the actor mechanism to send. 4. 4. Suppose Xiao Hong was in Guangzhou city before, and Xiao Ming used Xiao Hong's ID card to send a letter to Xiao Hong. Advanced post obtains Xiao Hong's residence permit number and sends a letter to Xiao Hong. During this process of sending the letter, Xiaohong moved, from Guangzhou to Shenzhen, at which time Xiaohong reported her new residence permit on the central government. When the letter from the senior post arrives in Guangzhou, it is found that Xiao Hong is not in Guangzhou. Then the senior postal service will go to the central government again to get Xiao Hong's residence permit and resend it, which may succeed or fail again, this process will be repeated several times, if it is unsuccessful, it will tell Xiao Ming that the letter has failed to be sent. 5. senior postal mail is more expensive, and people do not move a lot, usually Xiao Ming will remember Xiao Hong's residence permit after sending letters with senior postal mail, and next time send letters directly with the residence permit, and then send letters with senior postal mail if it fails. There are two kinds of return receipts, one without content, just means Xiao Hong received the letter, and one with Xiao Hong's return letter. Xiaoming can choose which return receipt form to use when he sends the letter. Xiao Ming cannot send two letters to Xiao Hong at the same time, he must wait for Xiao Hong's acknowledgement to arrive before Xiao Ming can continue sending the letter. ================================================ FILE: Book/5.5Actor Location-ZH.md ================================================ # Actor Location ### Actor Location Actor模型只需要知道对方的InstanceId就能发送消息,十分方便,但是有时候我们可能无法知道对方的InstanceId,或者是一个Actor的InstanceId会发生变化。这种场景很常见,比如:很多游戏是分线的,一个玩家可能从1线换到2线,还有的游戏是分场景的,一个场景一个进程,玩家从场景1进入到场景2。因为做了进程迁移,玩家对象的InstanceId也就变化了。ET提供了给这类对象发送消息的机制,叫做Actor Location机制。其原理比较简单: 1. 因为InstanceId是变化的,对象的Entity.Id是不变的,所以我们首先可以想到使用Entity.Id来发送actor消息 2. 提供一个位置进程(Location Server),Actor对象可以将自己的Entity.Id跟InstanceId作为kv存到位置进程中。发送Actor消息前先去位置进程查询到Actor对象的InstanceId再发送actor消息。 3. Actor对象在一个进程创建时或者迁移到一个新的进程时,都需要把自己的Id跟InstanceId注册到Location Server上去 4. 因为Actor对象是可以迁移的,消息发过去有可能Actor已经迁移到其它进程上去了,所以发送Actor Location消息需要提供一种可靠机制 5. ActorLocationSender提供两种方法,Send跟Call,Send一个消息也需要接受者返回一个消息,只有收到返回消息才会发送下一个消息。 6. Actor对象如果迁移走了,这时会返回Actor不存在的错误,发送者收到这个错误会等待1秒,然后重新去获取Actor的InstanceId,然后重新发送,目前会尝试5次,5次过后,抛出异常,报告错误 7. ActorLocationSender发送消息不会每次都去查询Location Server,因为对象迁移毕竟比较少见,只有第一次去查询,之后缓存InstanceId,以后发送失败再重新查询。 8. Actor对象在迁移过程中,有可能其它进程发送过来消息,这时会发生错误,所以location server提供了一种Lock的机制。对象在传送前,删掉在本进程的信息,然后在location server上加上锁,一旦锁上后,其它的对该key的请求会进行队列。 9. 传送前因为对方删除了本进程的actor,所以其它进程会发送失败,这时候他们会进行重试。重试的时候会重新请求location server,这时候会发现被锁了,于是一直等待 10. 传送完成后,要unlock location server上的锁,并且更新新的地址,然后响应其它的location请求。其它发给这个actor的请求继续进行下去。 注意,Actor模型是纯粹的服务端消息通信机制,跟客户端是没什么关系的,很多用ET的新人看到ET客户端消息也有Actor接口,以为这是客户端跟服务端通信的机制,其实不是的。ET客户端使用这个Actor完全是因为Gate需要对客户端消息进行转发,我们可以正好利用服务端actor模型来进行转发,所以客户端有些消息也是继承了actor的接口。假如我们客户端不使用actor接口会怎么样呢?比如,Frame_ClickMap这个消息 ```protobuf message Frame_ClickMap // IActorLocationMessage { int64 ActorId = 93; int64 Id = 94; float X = 1; float Y = 2; float Z = 3; } ``` 我们可能就不需要ActorId这个字段,消息发送到Gate,gate看到是Frame_ClickMap消息,它需要转发给Map上的Unit,转发还好办,gate可以从session中获取对应的map的unit的位置,然后转发,问题来了,Frame_ClickMap消息到了map,map怎么知道消息需要给哪个对象呢?这时候有几种设计: 1. 在转发的底层协议中带上unit的Id,需要比较复杂的底层协议支持。 2. 用一个消息对Frame_ClickMap消息包装一下,包装的消息带上Unit的Id,用消息包装意味着更大的消耗,增加GC。 个人感觉这两种都很差,不好用,而且就算分发给unit对象处理了,怎么解决消息重入的问题呢?unit对象仍然需要挂上一个消息处理队列,然后收到消息扔到队列里面。这不跟actor模型重复了吗?目前ET在客户端发给unit的消息做了个设计,消息做成actor消息,gate收到发现是actor消息,直接发到对应的actor上,解决的可以说很漂亮。其实客户端仍然是使用session.send跟call发送消息,发送的时候也不知道消息是actor消息,只有到了gate,gate才进行了判断,参考OuterMessageDispatcher.cs ### Actor Location消息的处理 ActorLocation消息发送 ```csharp // 从Game.Scene上获取ActorLocationSenderComponent,然后通过Entity.Id获取ActorLocationSender ActorLocationSender actorLocationSender = Game.Scene.GetComponent().Get(unitId); // 通过ActorLocationSender来发送消息 actorLocationSender.Send(actorLocationMessage); // 发送Rpc消息 IResponse response = await actorLocationSender.Call(actorLocationRequest); ``` ActorLocation消息的处理跟Actor消息几乎一样,不同的是继承的两个抽象类不同,注意actorlocation的抽象类多了个Location ```csharp // 处理send过来的消息, 需要继承AMActorLocationHandler抽象类,抽象类第一个泛型参数是Actor的类型,第二个参数是消息的类型 [ActorMessageHandler(AppType.Map)] public class Frame_ClickMapHandler : AMActorLocationHandler { protected override ETTask Run(Unit unit, Frame_ClickMap message) { Vector3 target = new Vector3(message.X, message.Y, message.Z); unit.GetComponent().MoveTo(target).Coroutine(); } } // 处理Rpc消息, 需要继承AMActorRpcHandler抽象类,抽象类第一个泛型参数是Actor的类型,第二个参数是消息的类型,第三个参数是返回消息的类型 [ActorMessageHandler(AppType.Map)] public class C2M_TestActorRequestHandler : AMActorLocationRpcHandler { protected override async ETTask Run(Unit unit, C2M_TestActorRequest message, Action reply) { reply(new M2C_TestActorResponse(){Info = "actor rpc response"}); await ETTask.CompletedTask; } } ``` ### ET的actor跟actor location的比喻 中国有很多城市(进程),城市中有很多人(entity对象)居住,每个人都有身份证号码(Entity.Id)。一个人每到一个市都需要办理居住证,分配到唯一的居住证号码(InstanceId),居住证号码的格式是2个字节市编号+4个字节时间+2个字节递增。身份证号码是永远不会变化的,但是居住证号码每到一个城市都变化的。 现在有个中国邮政(actor)。假设小明要发信给女朋友小红 1. 小红为了收信,自己必须挂载一个邮箱(MailboxComponent),小红收到消息就会处理。注意这里处理是一个个进行处理的。有可能小红会同时收到很多人的信。但是她必须一封一封的信看,比方说小明跟小宝都发了信给小红,小红先收到小明的信,再收到了小宝的信。小红先读小明的信,小明信中让小红给外婆打个电话(产生协程)再给自己回信,注意这期间小红也不能读下一封信,必须打完电话后才能读小宝的信。当然小红自己可以选择不处理完成就开始读小宝的信,做法是小红开一个新的协程来处理小明的信。 2. 假设小明知道小红的居住证号码,那么邮政(actor)可以根据居住证号码头两位找到小红居住的城市(进程),然后再根据小红的居住证编号,找到小红,把消息投递到小红的邮箱(MailboxComponent)中。这种是最简单的原生的actor模型 3. ET还支持了一套actor location机制。假设小明不知道小红的居住证号码,但是他知道小红的身份证号码,怎么办呢?邮政开发了一套高级邮政(actor location)想了一个办法,如果一个人经常搬家,它还想收到信,那他到一个新的城市都必须把自己的居住证跟身份证上报到中央政府(location server),这样高级邮政能够通过身份证号码来发送邮件。方法就是去中央政府拿到小红的居住证号码,再利用actor机制发送。 4. 假设小红之前在广州市,小明用小红的身份证给小红发信件了。 高级邮政获取了小红的居住证号码,给小红发信。发信的这个过程中,小红搬家了,从广州搬到了深圳,这时小红在中央政府上报了自己新的居住证。 高级邮政的信送到到广州的时候发现,小红不在广州。那么高级邮政会再次去中央政府获取小红的居住证,重新发送,有可能成功有可能再次失败,这个过程会重复几次,如果一直不成功则告诉小明,信件发送失败了。 5. 高级邮政发信比较贵,而且人搬家的次数并不多,一般小明用高级邮政发信后会记住小红的居住证,下次再发的时候直接用居住证发信,发送失败了再使用高级邮政发信。 6. 高级邮政的信都是有回执的,有两种回执,一种回执没有内容,只表示小红收到了信,一种回执带了小红的回信。小明在发信的时候可以选择使用哪种回执形式。小明给小红不能同时发送两封信,必须等小红的回执到了,小明才能继续发信。 ================================================ FILE: Book/5.6Numerical component design.md ================================================ Similar to world of warcraft, moba such skills are extremely complex, flexibility requires a very high skill system, must need a set of its flexible numerical structure to match. Numerical structure is well designed, the realization of the skill system will be very simple, otherwise it is a disaster. For example, in World of Warcraft, a character has many numerical attributes, such as movement speed, strength, anger, energy, concentration value, magic value, blood, maximum blood, physical attack, physical defense, spell attack, spell defense, etc. There are dozens of attributes. Attributes and attributes affect each other, buffs will add absolute value to attributes, increase the percentage, or some kind of buff will come back to you after counting all the increased value and doubling it. ## Common practice: The general is to write a value class. ```c# class Numeric { public int Hp; public int MaxHp; public int Speed; // Energy public int Energy; public int MaxEnergy; // Magic public int Mp; public int MaxMp; ..... } ``` On second thought, I'm a thief using energy why should I have a value of Mp? I am a mage using magic why should there be a field for energy? I'm not sure what to do with this, just pretend you didn't see it? I can not, I come to an inheritance? ```C# // Mage values calss MageNumeric: Numeric { // magic public int Mp; public int MaxMp; } // Thief value calss RougeNumeric: Numeric { // Energy public int Energy; public int MaxEnergy; } ```` 10 races, each race 7, 8 kinds of heroes, just these values class inheritance relationship, you have to be confused it. Object-oriented is difficult to adapt to the needs of this flexible and complex. And look at the Numeric class, each value can not just design a field, for example, I have a buff will increase 10 points Speed, and a kind of buff to increase 50% of the speed, then I must add at least three secondary attribute fields ```c# class Numeric { // speed final value public int Speed; // Speed initial value public int SpeedInit; // Speed increase value public int SpeedAdd; // Speed increase percentage value public int SpeedPct; } ``` After SpeedAdd and SpeedPct are changed, a calculation is performed to calculate the final speed value. buff only needs to go to modify SpeedAdd and SpeedPct on the line. ```c# Speed = (SpeedInit + SpeedAdd) * (100 + SpeedPct) / 100 ``` Each property may have several indirect effects on the value, you can think about how large this class is, a rough estimate of more than 100 fields. The trouble is that the formula is basically the same, but just can not be unified into a function, such as MaxHp, also has a buff effect ```c# class Numeric { public int Speed; public int SpeedInit; public int SpeedAdd; public int SpeedPct; public int MaxHp; public int MaxHpInit; public int MaxHpAdd; public int MaxHpPct; } ``` Also have to write a formula for calculating Hp ```c# MaxHp = (MaxHpInit + MaxHpAdd) * (100 + MaxHpPct) / 100 ``` Dozens of properties, you have to write dozens of times, and each secondary property changes to correctly call the corresponding formula calculation. Very troublesome! This design also has a big problem, buff configuration table to fill the corresponding attribute field is not very good to fill, for example, sprint buff (increase speed 50%), how to configure the buff table to make the program simple to find and operate the SpeedPct field? Not a good idea. ## ET framework uses the Key Value form to save the value of the property ```c# Using System.Collections.Generic; Generic; namespace Model Generic; namespace Model { public enum NumericType { Max = 10000, Speed = 1000, SpeedBase = Speed * 10 + 1, SpeedAdd = Speed * 10 + 2, SpeedPct = Speed * 10 + 3, SpeedFinalAdd = Speed * 10 + 4, SpeedFinalPct = Speed * 10 + 5, Hp = 1001, HpBase = Hp * 10 + 1, MaxHp = 1002, MaxHpBase = MaxHp * 10 + 1, MaxHpAdd = MaxHp * 10 + 2, MaxHpPct = MaxHp * 10 + 3, MaxHpFinalAdd = MaxHp * 10 + 4, MaxHpFinalPct = MaxHp * 10 + 5, } public class NumericComponent: Component { public readonly Dictionary NumericDic = new Dictionary(); public void Awake() { // initialize base value here } public float GetAsFloat(NumericType numericType) { return (float)GetByKey((int)numericType) / 10000; } public int GetAsInt(NumericType numericType) { return GetByKey((int)numericType); } public void Set(NumericType nt, float value) { this[nt] = (int) (value * 10000); } public void Set(NumericType nt, int value) { this[nt] = value; } public int this[NumericType numericType] { get { return this.GetByKey((int) numericType); } set { int v = this.GetByKey((int) numericType); if (v == value) { return; } NumericDic[(int)numericType] = value; Update(numericType); } } private int GetByKey(int key) { int value = 0; This.NumericDic.TryGetValue(key, out value); return value; } public void Update(NumericType numericType) { if (numericType > NumericType.Max) { return; } int final = (int) numericType / 10; int bas = final * 10 + 1; int add = final * 10 + 2; int pct = final * 10 + 3; int finalAdd = final * 10 + 4; int finalPct = final * 10 + 5; // A value may be affected by a variety of circumstances, such as speed, adding a buff may increase the speed of the absolute value of 100, but also some buffs increase the speed of 10%, so a value can be controlled by 5 values of the final result // final = (((base + add) * (100 + pct) / 100) + finalAdd) * (100 + finalPct) / 100; this.NumericDic[final] = ((this.GetByKey(base) + this.GetByKey(add)) * (100 + this.GetByKey(pct)) / 100 + this.GetByKey(finalAdd)) * (100 + this. GetByKey(finalPct)) / 100; Game.EventSystem.Run(EventIdType.NumbericChange, this.Entity.Id, numericType, final); } } } ``` 1. values are saved with key value, key is the type of value, defined by NumericType, value are integers, float type can also be converted to integers, for example, multiply by 1000; key value to save properties will become very flexible, for example, mage no energy properties, then initialize the mage object does not add energy key value It's fine. Thieves do not have mana, no spell damage, etc., the initialization will not need to add these. 2. world of warcraft, a value by 5 values to influence, you can unify the use of a formula. ``` final = (((base + add) * (100 + pct) / 100) + finalAdd) * (100 + finalPct) / 100; ``` For example, the speed value speed, there is an initial value speedbase, there is a buff1 to increase the absolute speed by 10 points, then buff1 will add 10 to speedadd when it is created, buff1 minus 10 to speedadd when it is deleted, buff2 increases the speed by 20%, then buff2 adds to speedpct when it is created The 5 values are changed and the corresponding properties can be recalculated by using the Update function in a unified way. buff configuration is quite simple. If the corresponding NumericType is filled in the buff configuration, the program can easily manipulate the corresponding value. 3. Changes in properties can be uniformly thrown to other modules to subscribe to the event, writing a property change monitor becomes very simple. For example, the achievement module needs to develop an achievement life value over 1000, will get the achievement of longevity master. Then the person developing the achievement module will subscribe to the HP changes as follows. ``` /// Monitor hp value changes [NumericWatcher(NumericType.Hp) public class NumericWatcher_Hp : INumericWatcher { public void Run(long id, int value) { if (value > 1000) { // get achievement longevity master achievement } } } ``` Similarly, recording an exception log for a gold change greater than 10,000 at a time, etc. can be done this way. With this numerical component, a moba skill system can be said to be half complete. ================================================ FILE: Book/5.6数值组件设计.md ================================================ 类似魔兽世界,moba这种技能极其复杂,灵活性要求极高的技能系统,必须需要一套及其灵活的数值结构来搭配。数值结构设计好了,实现技能系统就会非常简单,否则就是一场灾难。比如魔兽世界,一个人物的数值属性非常之多,移动速度,力量,怒气,能量,集中值,魔法值,血量,最大血量,物理攻击,物理防御,法术攻击,法术防御,等等多达几十种之多。属性跟属性之间又相互影响,buff又会给属性增加绝对值,增加百分比,或者某种buff又会在算完所有的增加值之后再来给你翻个倍。 ## 普通的做法: 一般就是写个数值类: ```c# class Numeric { public int Hp; public int MaxHp; public int Speed; // 能量 public int Energy; public int MaxEnergy; // 魔法 public int Mp; public int MaxMp; ..... } ``` 仔细一想,我一个盗贼使用的是能量,为什么要有一个Mp的值?我一个法师使用的是魔法为什么要有能量的字段?纠结这个搞毛,当作没看见不就行了吗?实在不行,我来个继承? ```C# // 法师数值 calss MageNumeric: Numeric { // 魔法 public int Mp; public int MaxMp; } // 盗贼数值 calss RougeNumeric: Numeric { // 能量 public int Energy; public int MaxEnergy; } ``` 10个种族,每个种族7,8种英雄,光这些数值类继承关系,你就得懵逼了吧。面向对象是难以适应这种灵活的复杂的需求的。 再来看看Numeric类,每种数值可不能只设计一个字段,比如说,我有个buff会增加10点Speed,还有种buff增加50%的speed,那我至少还得加三个二级属性字段 ```c# class Numeric { // 速度最终值 public int Speed; // 速度初始值 public int SpeedInit; // 速度增加值 public int SpeedAdd; // 速度增加百分比值 public int SpeedPct; } ``` SpeedAdd跟SpeedPct改变后,进行一次计算,就可以算出最终的速度值。buff只需要去修改SpeedAdd跟SpeedPct就行了。 ```c# Speed = (SpeedInit + SpeedAdd) * (100 + SpeedPct) / 100 ``` 每种属性都可能有好几种间接影响值,可以想想这个类是多么庞大,初略估计得有100多个字段。麻烦的是计算公式基本一样,但是就是无法统一成一个函数,例如MaxHp,也有buff影响 ```c# class Numeric { public int Speed; public int SpeedInit; public int SpeedAdd; public int SpeedPct; public int MaxHp; public int MaxHpInit; public int MaxHpAdd; public int MaxHpPct; } ``` 也得写个Hp的计算公式 ```c# MaxHp=(MaxHpInit + MaxHpAdd) * (100 + MaxHpPct) / 100 ``` 几十种属性,就要写几十遍,并且每个二级属性改变都要正确调用对应的公式计算. 非常麻烦! 这样设计还有个很大的问题,buff配置表填对应的属性字段不是很好填,例如疾跑buff(增加速度50%),在buff表中怎么配置才能让程序简单的找到并操作SpeedPct字段呢?不好搞。 ## ET框架采用了Key Value形式保存数值属性 ```c# using System.Collections.Generic; namespace Model { public enum NumericType { Max = 10000, Speed = 1000, SpeedBase = Speed * 10 + 1, SpeedAdd = Speed * 10 + 2, SpeedPct = Speed * 10 + 3, SpeedFinalAdd = Speed * 10 + 4, SpeedFinalPct = Speed * 10 + 5, Hp = 1001, HpBase = Hp * 10 + 1, MaxHp = 1002, MaxHpBase = MaxHp * 10 + 1, MaxHpAdd = MaxHp * 10 + 2, MaxHpPct = MaxHp * 10 + 3, MaxHpFinalAdd = MaxHp * 10 + 4, MaxHpFinalPct = MaxHp * 10 + 5, } public class NumericComponent: Component { public readonly Dictionary NumericDic = new Dictionary(); public void Awake() { // 这里初始化base值 } public float GetAsFloat(NumericType numericType) { return (float)GetByKey((int)numericType) / 10000; } public int GetAsInt(NumericType numericType) { return GetByKey((int)numericType); } public void Set(NumericType nt, float value) { this[nt] = (int) (value * 10000); } public void Set(NumericType nt, int value) { this[nt] = value; } public int this[NumericType numericType] { get { return this.GetByKey((int) numericType); } set { int v = this.GetByKey((int) numericType); if (v == value) { return; } NumericDic[(int)numericType] = value; Update(numericType); } } private int GetByKey(int key) { int value = 0; this.NumericDic.TryGetValue(key, out value); return value; } public void Update(NumericType numericType) { if (numericType > NumericType.Max) { return; } int final = (int) numericType / 10; int bas = final * 10 + 1; int add = final * 10 + 2; int pct = final * 10 + 3; int finalAdd = final * 10 + 4; int finalPct = final * 10 + 5; // 一个数值可能会多种情况影响,比如速度,加个buff可能增加速度绝对值100,也有些buff增加10%速度,所以一个值可以由5个值进行控制其最终结果 // final = (((base + add) * (100 + pct) / 100) + finalAdd) * (100 + finalPct) / 100; this.NumericDic[final] = ((this.GetByKey(bas) + this.GetByKey(add)) * (100 + this.GetByKey(pct)) / 100 + this.GetByKey(finalAdd)) * (100 + this.GetByKey(finalPct)) / 100; Game.EventSystem.Run(EventIdType.NumbericChange, this.Entity.Id, numericType, final); } } } ``` 1.数值都用key value来保存,key是数值的类型,由NumericType来定义,value都是整数,float型也可以转成整数,例如乘以1000;key value保存属性会变得非常灵活,例如法师没有能量属性,那么初始化法师对象不加能量的key value就好了。盗贼没有法力值,没有法术伤害等等,初始化就不用加这些。 2.魔兽世界中,一个数值由5个值来影响,可以统一使用一条公式: ``` final = (((base + add) * (100 + pct) / 100) + finalAdd) * (100 + finalPct) / 100; ``` 比如说速度值speed,有个初始值speedbase,有个buff1增加10点绝对速度,那么buff1创建的时候会给speedadd加10,buff1删除的时候给speedadd减10,buff2增加20%的速度,那么buff2创建的时候给speedpct加20,buff2删除的时候给speedpct减20.甚至可能有buff3,会在最终值上再加100%,那么buff3将影响speedfinalpct。这5个值发生改变,统一使用Update函数就可以重新计算对应的属性了。buff配置中对应数值字段相当简单,buff配置中填上相应的NumericType,程序很轻松就能操作对应的数值。 3.属性的改变可以统一抛出事件给其它模块订阅,写一个属性变化监视器变得非常简单。例如成就模块需要开发一个成就生命值超过1000,会获得长寿大师的成就。那么开发成就模块的人将订阅HP的变化: ``` /// 监视hp数值变化 [NumericWatcher(NumericType.Hp)] public class NumericWatcher_Hp : INumericWatcher { public void Run(long id, int value) { if (value > 1000) { //获得成就长寿大师成就 } } } ``` 同理,记录一次金币变化大于10000的异常日志等等都可以这样做。 有了这个数值组件,一个moba技能系统可以说已经完成了一半。 **代码地址:https://github.com/egametang/Egametang** ================================================ FILE: Book/6.1AI Framwork.md ================================================ # AI framework ## 1. Several AI designs AI in the game a lot, but why do people always feel ai writing up very difficult, I later thought about it, the main reason is the use of improper methods. Before people write ai mainly have several options. ### a. State machine I do not know who came up with this approach, really powerless to complain. Originally any data on the object is the state, this method and to define some state into a new kind of node, the object on the state change will cause the conversion between nodes, the implementation of the corresponding method, such as OnEnter OnExit and so on. Here is an example of a monster, monsters can be divided into a variety of states, patrol, attack, chase, return. The state changes of the monster are: Patrol->Chase Patrol state found a distant enemy to chase state Patrol->Attack Patrol find enemy can be attacked to attack state Attack->Chase The attack state finds an enemy in the distance and goes after it. Attack->Return Attack state find the enemy is too far to return state chase->return chase state found too far from the enemy to return to the state There are so many state transitions that it's hard to find out if I've missed them here. Once there are more nodes, any two nodes may need to be connected, and it will become a super complex mesh structure, the complexity is the square of N, and it is very difficult to maintain. In order to solve the problem of complex mesh structure and then upgraded to a hierarchical state machine and so on. Of course, various patching methods still do not solve the essential problem. It is not your problem to use bad state machines, it is the problem of state machines. ### b. Behavior tree The ai of the behavior tree is responsive ai, the tree from top to bottom (or from left to right execution, here from top to bottom for example) is actually the action node ranked a priority, the action above the first to determine whether to meet the conditions, meet the implementation. We won't go into details here. The complexity of the behavior tree is N, greatly simplified than the state machine, but there are still many defects, ai too complex when the tree will become very large, and difficult to reconfigure. For example, in our own project, we want to make a robot ai similar to a human, automatically do tasks, fight monsters, play the system in the game, chat with people, and even attack others. Imagine how complex this tree will become! Another drawback of the behavior tree is that some action nodes are a persistent process, that is to say, a concurrent process, behavior tree management up concurrent process is not very good, such as the above example, need to move to the target side, this move is made into a concurrent process it, or every frame move it? This is a difficult problem, how to do it is not comfortable. ## 2. my approach What is ai? Very simple, ai is constantly based on the current state, perform the appropriate behavior. Remember these two sentences, it is important, this is the essence of ai! These two sentences are divided into two parts, one is the state judgment, the second is the execution of behavior. State judgment is well understood, what is the behavior? Take the above example of the monster state machine, the behavior of the monster is Patrol, attack the enemy, return to the patrol point. For example. Patrol (when the monster is within the patrol range, there is no enemy around, choose the next patrol point, move) attack the enemy (when the monster found within the guard range of the enemy, if the attack distance enough to attack, not enough to move over to attack) Return (when the monster found more than a certain distance from the birth point, plus the invincibility buff, move to the birth point, to the birth point, remove the invincibility buff) Unlike the state machine, these three state changes do not care about what the last state is, only about whether the current conditions are met, meet the implementation of the behavior. Behavior may be able to perform instantly, but also may be a continuous process, such as patrol, choose the next patrol point to move over, go to a point and then choose a point, and so on and so forth. For example, attacking the enemy, may need to move to the target to attack. How to design this ai framework? Here it is very simple, abstract ai nodes, each node contains a conditional judgment, with the implementation of behavior. The behavior method should be a concurrent process ```csharp public class AINode { public virtual bool Check(Unit unit) // test whether the condition is met { } public virtual ETTask Run(Unit unit) { } } ``` Thinking further, if the monster is on patrol and finds an enemy, then the monster should interrupt the current patrol and go on to perform the act of attacking the enemy instead. So our behavior should need to support being interrupted, which means that the behavior concurrent should support cancellation, and this is especially important to note that any concurrent in the behavior Run method should support the cancellation operation! ```csharp public class AINode { public virtual bool Check(Unit unit) { } public virtual ETVoid Run(Unit unit, ETCancelToken cancelToken) { } } ```` Implement three ai nodes XunLuoNode(patrol) GongjiNode(attack) FanHuiNode(return) ```csharp public class XunLuoNode: AINode { public virtual bool Check(Unit unit) { if (not in patrol range) { return false; } if (there are enemies around) { return false; } return true; } public virtual ETVoid Run(Unit unit, ETCancelToken cancelToken) { while (true) { Vector3 nextPoint = FindNextPoint(); bool ret = await MoveToAsync(nextPoint, cancelToken); // move to the target point, return false means the process is canceled if (!ret) { return; } // stay for two seconds, note that any concurrent process must be able to be cancelled here Wait(2000, cancelToken). bool ret = await TimeComponent; if (!ret) { return; } } } } ``` The same can be achieved for the other two nodes. It's not enough to design the nodes, you also need to string the nodes together so that the ai can rotate ```csharp AINode[] aiNodes = {xunLuoNode, gongjiNode, fanHuiNode}; AINode current; ETCancelToken cancelToken; while(true) { // Every second you need to re-determine if the new behavior is satisfied, this time can be set by yourself await TimeComponent.Instance.Wait(1000); AINode next; foreach(var node in aiNodes) { if (node.Check()) { next = node; break; } } if (next == null) { continue; } // If the next node is the same as the current one, then it is not executed if (next == current) { continue; } // Stop the current concurrent process cancelToken.Cancel(); // Execute the next concurrent process cancelToken = new ETCancelToken(); next.Run(unit, cancelToken).Coroutine(); } ``` This code is very simple, meaning that it iterates through the nodes every second until it finds a node that satisfies the conditions and then executes it, waiting for the next second to determine, before executing the next node, interrupting the currently executing concurrent process. A few misconceptions about the use: 1. behavior if there is a concurrent process must be able to cancel, and pass in cancelToken, otherwise something will go wrong, because once the monster meets the execution of the next node, you need to cancel the current concurrent process. 2. different from the behavior tree and state machine, the role of the node is only a piece of logic, the node does not need to share. Shared is the concurrent methods, such as MoveToAsync, monster patrol node can be used, the monster attack enemy node in pursuit of the enemy can also be used. 3. nodes can do very large, such as automatically do the task node, move to the npc, pick up the task, according to the task of subtasks to do subtasks, such as moving to the monster point to fight monsters, move to the collection of things to collect, etc., after doing all the subtasks, move to the task npc to turn in the task. All of this is written in a while loop, using a concurrent string. Thinking about a big question, how do you design a piezo bot? What does a piezo bot need to do? Automatically do tasks, automatically play various systems, automatically attack enemies, will counterattack, will find people to chat, etc.. Just make an ai node for each of the above mentioned. Brothers, AI simple or not? ================================================ FILE: Book/6.1AI框架.md ================================================ # AI框架 ## 1. 几种AI的设计 AI在游戏中很多,但是为什么大家总是感觉ai编写起来十分困难,我后来思考了一番,主要原因是使用的方法不当。之前大家编写ai主要有几种方案: ### a. 状态机 我是不知道谁想出来这个做法的,真是无力吐槽。本来对象身上任何数据都是状态,这种方法又要把一些状态定义成一种新的节点,对象身上状态变化会引起节点之间的转换,执行对应的方法,比如OnEnter OnExit等等。这里以怪物来举例,怪物可以分为多种状态,巡逻,攻击,追逐,返回。怪物的状态变化有: 巡逻->追逐 巡逻状态发现远处有敌人变追逐状态 巡逻->攻击 巡逻发现可以攻击敌人变攻击状态 攻击->追逐 攻击状态发现敌人有段距离于是去追逐 攻击->返回 攻击状态发现距离敌人过远变返回状态 追逐->返回 追逐状态发现距离敌人过远变返回状态 太多状态转换了,这里有没有漏掉我已经难以发现了。一旦节点更多,任何两个节点都可能需要连接,将成为超级复杂的网状结构,复杂度是N的平方级,维护起来十分困难。为了解决网状结构变复杂的问题于是又升级为分层状态机等等。当然各种打补丁的方法还是没能解决本质的问题。用不好状态机不是你们的问题,是状态机的问题。 ### b. 行为树 可能大家都觉得状态机解决复杂ai实在太困难了,于是有人想出了行为树来做ai。行为树的ai是响应式ai,这棵树从上往下(或者从左往右执行,这里以从上往下举例)实际上是把action节点排了个优先级,上面的action最先判断是否满足条件,满足则执行。这里就不详细讲了。行为树的复杂度是N,比状态机大大简化了,但是仍然存在不少缺陷,ai太复杂的时候,树会变得非常大,而且难以重构。比如我们自己项目,要做一个跟人差不多的机器人ai,自动做任务,打怪,玩游戏中的系统,跟人聊天,甚至攻击别人。想象一下,这颗树将变得多复杂!行为树的另外一个缺陷是某些action节点是个持久的过程,也就是说是个协程,行为树管理起协程起来不太好处理,比如上面的例子,需要移动到目标身边,这个移动究竟是做成协程呢,还是每帧move呢?这是个难题,怎么做都不舒服。 ## 2. 我的做法 ai是什么呢?很简单啊,ai就是不停的根据当前的状态,执行相应的行为。记住这两句话,很重要,这就是ai的本质!这两句话分成两部分,一是状态判断,二是执行行为。状态判断好理解,行为是啥?以上面状态机的怪物举例子,怪物的行为就是 巡逻,攻击敌人,返回巡逻点。比如: 巡逻 (当怪物在巡逻范围内,周围没有敌人,选择下一个巡逻点,移动) 攻击敌人 (当怪物发现警戒范围内有敌人,如果攻击距离够就攻击,不够就移动过去攻击) 返回 (当怪物发现离出生点超过一定距离,加上无敌buff,往出生点移动,到了出生点,删除无敌buff) 跟状态机不一样的是,这3个状态的变化完全不关心上一个状态是啥,只关心当前的条件是否满足,满足就执行行为。行为可能能瞬间执行,也可能是一段持续的过程,比如巡逻,选下一个巡逻点移动过去,走到了再选一个点,不停的循环。比如攻击敌人,可能需要移动到目标去攻击。 怎么设计这个ai框架呢?到这里就十分简单了,抽象出ai节点,每个节点包含条件判断,跟执行行为。行为方法应该是一个协程 ```csharp public class AINode { public virtual bool Check(Unit unit) // 检测条件是否满足 { } public virtual ETTask Run(Unit unit) { } } ``` 进一步思考,假如怪物在巡逻过程中,发现敌人,那么怪物应该要打断当前的巡逻,转而去执行攻击敌人的行为。因此我们行为应该需要支持被打断,也就是说行为协程应该支持取消,这点特别需要注意,行为Run方法中任何协程都要支持取消操作! ```csharp public class AINode { public virtual bool Check(Unit unit) { } public virtual ETVoid Run(Unit unit, ETCancelToken cancelToken) { } } ``` 实现三个ai节点 XunLuoNode(巡逻) GongjiNode(攻击) FanHuiNode(返回) ```csharp public class XunLuoNode: AINode { public virtual bool Check(Unit unit) { if (不在巡逻范围) { return false; } if (周围有敌人) { return false; } return true; } public virtual ETVoid Run(Unit unit, ETCancelToken cancelToken) { while (true) { Vector3 nextPoint = FindNextPoint(); bool ret = await MoveToAsync(nextPoint, cancelToken); // 移动到目标点, 返回false表示协程取消 if (!ret) { return; } // 停留两秒, 注意这里要能取消,任何协程都要能取消 bool ret = await TimeComponent.Instance.Wait(2000, cancelToken); if (!ret) { return; } } } } ``` 同理可以实现另外两个节点。光设计出节点还不行,还需要把各个节点串起来,这样ai才能转动 ```csharp AINode[] aiNodes = {xunLuoNode, gongjiNode, fanHuiNode}; AINode current; ETCancelToken cancelToken; while(true) { // 每秒中需要重新判断是否满足新的行为了,这个时间可以自己定 await TimeComponent.Instance.Wait(1000); AINode next; foreach(var node in aiNodes) { if (node.Check()) { next = node; break; } } if (next == null) { continue; } // 如果下一个节点跟当前执行的节点一样,那么就不执行 if (next == current) { continue; } // 停止当前协程 cancelToken.Cancel(); // 执行下一个协程 cancelToken = new ETCancelToken(); next.Run(unit, cancelToken).Coroutine(); } ``` 这段代码十分简单,意思就是每秒钟遍历节点,直到找到一个满足条件的节点就执行,等下一秒再判断,执行下一个节点之前,先打断当前执行的协程。 几个使用误区: 1. 行为中如果有协程必须能够取消,并且传入cancelToken,否则会出大事,因为怪物一旦满足执行下个节点,需要取消当前协程。 2. 跟行为树与状态机不同,节点的作用只是一块逻辑,节点并不需要共享。共享的是协程方法,比如MoveToAsync,怪物巡逻节点可以使用,怪物攻击敌人节点中追击敌人也可以使用。 3. 节点可以做的非常庞大,比如自动做任务节点,移动到npc,接任务,根据任务的子任务做子任务,比如移动到怪点打怪,移动到采集物去采集等等,做完所有子任务,移动到交任务npc交任务。所有的一切都是写在一个while循环中,利用协程串起来。 思考一个大问题,怎么设计一个压测机器人呢?压测机器人需要做到什么?自动做任务,自动玩各种系统,自动攻击敌人,会反击,会找人聊天等等。把上面说的每一条做成一个ai节点即可。兄弟们,AI简不简单? ================================================ FILE: Book/6.2AI框架-行为机.md ================================================ # AI框架介绍-行为机(Behavior Machine) ## 什么是行为机 顾名思义,类比状态机每个节点是一个状态,行为机每个节点是描述一种行为。行为机每个节点之间是互斥的,并且节点相互之间完全不用关心是怎么切换的。这里就不讲状态机跟行为树是怎么做ai的了,这里只讲用行为机怎么做一个ai。举个例子 mmo中的小怪策划案,大致会这么写: 小怪在出生点周围巡逻。发现周围有玩家则选择一个玩家做目标,追击该目标玩家,追到目标玩家则攻击目标玩家,发现距离出生点太远则返回,返回到出生点则继续巡逻 ### 1.定义ai的各种行为 我们首先定义好怪物有哪些行为。很简单,我们直接根据策划案中的字面意思,怪物大致有这么几种行为: a.巡逻 b.选择一个玩家追击并且攻击 e.返回出生点。 注意很多状态机会把移动作为一种状态,这在行为机中是不对的,因为巡逻,追击,返回都会有移动,移动只是玩家行为节点中的一个部分,移动跟巡逻,追击,返回并不是互斥的。 节点不要拆的太细,因为每个行为是个协程,我们可以在行为节点中写十分复杂的逻辑,比如有些同学可能会把 选择一个玩家追击并且攻击 这一个节点拆成 a 选择目标 b 追击目标 c 攻击目标. 甚至还有人会把巡逻拆的更细,拆成a.寻找一个点 b.移动 c.等待一定时间。这都是状态机跟行为树的思维,因为状态机跟行为树可能希望移动节点可以共用。这都是增加了麻烦,不合理,不要用状态机跟行为树的思维去想行为机。行为机中,节点只是描述一种行为,并不需要共用,共用的永远是各种函数。 ### 2.填充满足行为的条件 我们把每个行为确定好对应的条件,一旦条件满足则会进入该行为,取消上一个行为(协程) a. 巡逻的条件:身上没有玩家目标,周围没有玩家,距离出生点 < 10米 b. 选择一个玩家追击并且攻击: 周围有玩家,距离出生点 < 10米 c. 返回出生点: 距离出生点 > 20米 其实条件一旦列出,那么节点中的Check方法自然也就实现了 ### 3.实现行为 a. 巡逻的伪代码: ```csharp while (true) { pos = 出生点周围找一个点 bool ret = await MoveToAsync(pos,cancelToken); if (!ret) // false表示协程取消, 则需要return,停止整个协程 { return; } // 移动到了,随机等待2-4秒 randomTime = RandomHelper.Random(2000, 4000); bool ret = await TimeComponent.Instance.Wait(randomTime, cancelToken) if (!ret) // false表示协程取消, 则需要return,停止整个协程 { return; } } ``` 这样,如果b c条件不满足的话,怪物就永远在巡逻节点协程中,不停的找一个点移动,等待,移动,等待 b. 选择目标追击并且攻击目标节点的伪代码: ```csharp while (true) { target = SelectTarget() while (true) { while (true) { // 追击目标 pos = 计算离目标0.2米的一个点 // 这里不能以目标作为移动目标,因为怪物要距离玩家稍远一点 await MoveToAsync(pos, cancelToken); if (!ret) // false表示协程取消, 则需要return,停止整个协程 { return; } // 距离玩家 < 0.5米,表示追到了玩家,就不需要追了 if (距离玩家<0.5米) { break; } } // 追击到了,攻击玩家 while (true) { // spellId = SelectSpell(); if (spellId == 0) // 可能技能在cd,等待500ms再试 { bool ret = await TimeComponent.Instance.Wait(500, cancelToken) if (!ret) // false表示协程取消, 则需要return,停止整个协程 { return; } continue; } await CastSpell(target); // 攻击完成后停止一段时间 bool ret = await TimeComponent.Instance.Wait(1000, cancelToken) if (!ret) // false表示协程取消, 则需要return,停止整个协程 { return; } // 距离玩家 > 0.5米, 距离玩家远了,break攻击循环,继续追击 if (距离玩家<0.5米) { break; } } } // 这里加个time,防止上面两个while循环没有进入,结果就会导致一直执行 target = 选择一个目标 这句话,会导致死循环 bool ret = await TimeComponent.Instance.Wait(100, cancelToken) if (!ret) // false表示协程取消, 则需要return,停止整个协程 { return; } } ``` c. 返回出生点伪代码: ```csharp while (true) { // 整个返回过程是无敌的 using (Buff buff = AddBuff(无敌)) { pos = 找到离出生点10米的点 bool ret = await MoveToAsync(pos,cancelToken); if (!ret) // false表示协程取消, 则需要return,停止整个协程 { return; } // 移动到了, buff会删除,或者切换成其它状态,协程退出也会删除无敌buff } } ``` 其实巡逻跟返回两个节点也可以合并成一个节点,这个大家自己去尝试尝试。可以看出,行为机编写是非常简单,代码是非常易读的。就这么一个怪物的逻辑,用行为树来编写,节点就很多了,而且并不好阅读跟重构。整个逻辑可能还有些瑕疵,不过意思应该很明白了。 # 总结 1. 行为机节点并不需要共用,行为机的节点只是表示一段逻辑,可以做的非常非常非常庞大,比如做机器人的时候,一个做任务,只有一个节点。里面代码调用了无数的协程方法。 2. 行为机共用的是函数,不是节点,不要想着这个节点应该抽出来共用,这个想法是错误的。 3. 行为机永远只关注当前行为,永远不需要关心上一个行为,当满足行为条件就直接打断上个协程,执行当前节点的协程即可。所以只需要定义好玩家有哪些行为,ai自然而然的就写出来了。 4. 节点不要拆的太细,没有必要 ================================================ FILE: Book/7.1代码规范.md ================================================ # 代码规范 [分析器说明](https://www.yuque.com/u28961999/yms0nt/) ================================================ FILE: Book/8.1ET Package制作指南.md ================================================ # ET Package规范 1. ET Package是标准的Unity Package 2. ET Package包目录的格式是cn.etetet.+包名,例如cn.etetet.core 3. ET Package 本质上是npm包,ET使用github package功能来实现托管ET Packages,注意在package.json中添加 ``` "publishConfig": { "registry": "https://npm.pkg.github.com/@ET-Packages" } ``` 4. 在你们包的根目录加上.github/workflows/release-package.yml 这个文件可以从 https://github.com/ET-Packages/cn.etetet.yiui/tree/master/.github/workflows 复制过去,这是一个github action文件 5. 包的制作者在你们github仓库中,点开Actions,点击ET Package这个Action,然后点击右侧的Run workflow即可制作成package 6. 注意ET包的目录永远在Packages目录下,包安装完成后cn.etetet开头的包会自动移动到Packages目录 7. 包中特殊目录跟文件 a. Scripts 该目录放置Model Hotfix ModelView HotfixView代码,这些代码unity中可热更,这几个子目录下面的结构跟ET8.1完全一样,包含Server Client Share 分别表示在服务端 客户端 双端生效的代码 b. Runtime 该目录放置aot代码,里面都要定义asmdef,作为一个程序集使用,一般以ET.Core这种格式命名 c. Excel,该目录用来放置Excel表格,ExcelExporter工具会扫描这个目录,导出代码到cn.etetet.excel中去,导出配置放在ET/Config/Excel中,其中json格式放在ET/Config/Json中 d. Proto, 该目录放置消息定义,Proto2CS工具会扫描这个目录,生成代码在cn.etetet.proto包中 e. DotNet~ 该目录放置DotNet专用的工程跟代码,因为不能被Unity编辑器识别,所以用了~号 f. CodeMode 该目录类似Scripts目录,区别是Model Hotfix ModelView HotfixView目录下面是Server Client ClientServer,分别是不同CodeMode下使用的代码 Scripts中的Share代表双端都会使用的代码,而CodeMode目录下的ClientServer表示只有Unity编辑器使用ClientServer模式才会使用 g. Editor目录,表示这个包的Editor代码,一般命名是ET.Core.Editor这样命名 h. 其它目录例如Plugins Scenes等等并没有严格限制 i. 注意,每个Package在顶层放一个Ignore的asmdef,这样的好处是这个包的代码默认是不生效的,只有放了asmdef才生效 j. 每个包都放一个packagegit.json文件,里面定义了包的Id跟包名以及git依赖,包的id是统一由熊猫分配 8. 注意,假如package是一个完整的可运行的demo,需要包含DotNet~目录,里面放好Model跟Hotfix工程,需要将ET.sln工程复制到包中,这样方便使用者可以复制这个sln到根目录,双击使用 9. DotNet~目录中的csproj工程可以引用其它包的csproj,也可以引用nuget等,都没有限制 10. Model ModelView Hotfix HotfixView中的引用怎么配置?因为每个包都可能有这四个目录,每个包的4个目录引用的程序集都可能不一样,ET提供了一种方法,在packagegit.json中写好依赖,具体可以参考cn.etetet.core中的packagegit.json文件 因为最终这些目录会合并到demo的4个程序集中去,我们可以运行ET->Refresh来将所有的引用最终设置到demo的Model ModelView Hotfix HotfixView四个程序集的引用中去 11. 如果自己制作的包需要依赖其它git包,例如memorypack,那么可以在自己项目中的packagegit.json文件添加GitDependencies项,具体可以参考cn.etetet.core包 12. demo包制作需要给Model ModelView Hotfix HotfixView4个asmdef加上INITED宏,防止包一下载下来就生效,导致错误。只有在运行ET->Init后才会加上INITED宏,这样4个程序集才会生效 13. 注意为了让自定义菜单简洁统一,Package自定义的菜单使用"ET->Package名->具体功能" 这种风格,例如ET->Loader->Init 14. 为了控制打包裁剪问题,demo可以在自己包目录放置一个link.xml控制裁剪,init的时候会把link.xml链接到Assets目录,因为Unity只扫描Assets中的link.xml # ET Package付费包制作 1. 付费包的制作过程跟上面是一样的,区别是一踢公司(熊猫)会开通一个私有仓库给开发者,开发者制作出来的包也是私有的 2. 用户要安装包需要开通权限,用户需要找一踢公司(熊猫)付费开通,开通之后就能正常访问包跟仓库 3. 假如开发者的仓库不提供源码给用户,只提供包,这也是可行的,具体找一踢公司(熊猫)聊 4. 付费包双方职责:开发者制作包,维护包,确保包正常使用,一踢公司(熊猫)负责收费开通权限,维护包下载安装稳定,以及github packages的相关费用支出。包的定价由开发者跟一踢公司(熊猫)双方商量决定 5. 收入分成,开发者跟一踢公司(熊猫)分成比例是7:3,注意分成都是税前,如果开发者是公司,则7成扣除税后(假如有的话)直接转入开发者的公司账户,如果开发者是个人,一踢公司(熊猫)需要扣除税再支付给个人开发者 6. 结账周期是3个月结账一次 ================================================ FILE: Book/8.2ET Package目录.md ================================================ ET Package目录,简单描述,具体说明请看包中的Readme | 编号 | 包名 | 描述 | 价格(元) | |------|-------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------|-------| | 0001 | cn.etetet.core | et框架核心, 实现了纤程,网络,entity等et基础功能 | | | 0002 | cn.etetet.loader | et框架核心,是一个加载器,用于加载model modelview hotfix hotfixview | | | 0003 | cn.etetet.actorlocation | 实现了actor location机制 | | | 0004 | cn.etetet.move | mmo类型的移动组件 | | | 0005 | cn.etetet.unit | 提供一个包含位置旋转的entity,可以作为场景中的实体 | | | 0006 | cn.etetet.db | 一个简单的mongodb封装 | | | 0007 | cn.etetet.yooassets | 提供了yooassets的封装 | | | 0008 | cn.etetet.demores | et自带demo的一些资源 | | | 0009 | cn.etetet.login | 实现前后端登录流程,客户端网络是独立的纤程,服务端提供了realm gate scene | | | 0010 | cn.etetet.statesync | 状态同步demo | | | 0011 | cn.etetet.lockstep | 预测回滚的帧同步demo | | | 0012 | cn.etetet.proto | proto2cs工具,用于把proto文件导出成C# | | | 0013 | cn.etetet.excel | excelexporter工具,把excel导出成代码跟配置数据 | | | 0014 | cn.etetet.recast | 3d recast寻路库 | | | 0015 | cn.etetet.router | et的软路由,可以防网络攻击 | | | 0016 | cn.etetet.http | 简单的http库 | | | 0017 | cn.etetet.referencecollector | 一个unity脚本,可以挂在gameobject上实现资源引用 | | | 0018 | cn.etetet.console | 实现了控制台解析分发 | | | 0019 | cn.etetet.aoi | 一个九宫格的aoi实现 | | | 0020 | cn.etetet.sourcegenerator | et框架的分析器跟代码生成器 | | | 0021 | cn.etetet.lsentity | 帧同步的Entity实现,提供了lsupdate,lsentity lsworld等功能 | | | 0022 | cn.etetet.truesync | 帧同步用的定点数实现 | | | 0023 | cn.etetet.numeric | 一个数值组件,提供了kv的实现 | | | 0024 | cn.etetet.mathematics | 一个对unity的mathematics包装,主要是增加对服务端dotnet的支持 | | | 0025 | cn.etetet.ui | et框架实现的一个简单UI | | | 0026 | cn.etetet.watcher | 用于监视进程,拉起进程,防止进程挂掉 | | | 0027 | cn.etetet.robotcase | 测试用例组件 | | | 0028 | cn.etetet.netinner | 内网消息模块 | | | 0029 | cn.etetet.startconfig | 服务器配置 | | | 0030 | cn.etetet.memorypack | memorypack | | | 0031 | cn.etetet.hybridclr | hybridclr热更新支持 | | | 0032 | cn.etetet.ai | 行为机模块,用来写ai非常方便 | | | 0033 | cn.etetet.webgl | webgl支持 |999 | | 0034 | cn.etetet.mongodb | 数据库补丁包 |100 | | | [1000-1999 (YIUI Package目录)](https://lib9kmxvq7k.feishu.cn/wiki/XJxLwzTlViqD5TkSAw1c32Rqnd1) | | | | 1001 | [cn.etetet.yiuiframework](https://github.com/ET-Packages/cn.etetet.yiuiframework) | [框架](https://lib9kmxvq7k.feishu.cn/wiki/ES7Gwz4EAiVGKSkotY5cRbTznuh) | | | 1100 | [cn.etetet.packagemanager](https://github.com/ET-Packages/cn.etetet.packagemanager) | [包管理](https://lib9kmxvq7k.feishu.cn/wiki/DzqwwwBJvixRvtkCI4dcatGcnAd) | | | 1101 | [cn.etetet.yiuistatesync](https://github.com/ET-Packages/cn.etetet.yiuistatesync) | [YIUIDemo 运行指南](https://lib9kmxvq7k.feishu.cn/wiki/H7SmwXozNiliN3kahZFcqQxqnub) | | | 1201 | [cn.etetet.yiuiloopscrollrectasync](https://github.com/ET-Packages/cn.etetet.yiuiloopscrollrectasync) | [无限循环列表 (异步)](https://lib9kmxvq7k.feishu.cn/wiki/HPbwwkhsKi9aDik5VEXcqPhDnIh) | | | 1202 | [cn.etetet.yiuiloopscrollrectsync](https://github.com/ET-Packages/cn.etetet.yiuiloopscrollrectsync) | [无限循环列表 (同步)](https://lib9kmxvq7k.feishu.cn/wiki/HPbwwkhsKi9aDik5VEXcqPhDnIh) | | | 1203 | [cn.etetet.yiuiyooassets](https://github.com/ET-Packages/cn.etetet.yiuiyooassets) | [YooAsset 资源管理](https://lib9kmxvq7k.feishu.cn/wiki/SUpUwiABuip53zkWEdwcITACntc) | | | 1204 | [cn.etetet.yiuiinvoke](https://github.com/ET-Packages/cn.etetet.yiuiinvoke) | [Invoke 文档](https://lib9kmxvq7k.feishu.cn/wiki/TpyYwbWIUizhfKkcubocTZgInse) | | | 1301 | [cn.etetet.yiuigm](https://github.com/ET-Packages/cn.etetet.yiuigm) | [GM命令](https://lib9kmxvq7k.feishu.cn/wiki/NYADwMydliVmQ7kWXOuc0yxGn7p) | | | 1302 | [cn.etetet.yiuitips](https://github.com/ET-Packages/cn.etetet.yiuitips) | [Tips](https://lib9kmxvq7k.feishu.cn/wiki/OdNgwu0KsiyJ6NkK8vCcwbjbn1g) | | | 1303 | [cn.etetet.yiui3ddisplay](https://github.com/ET-Packages/cn.etetet.yiui3ddisplay) | [UI3D模型展示](https://lib9kmxvq7k.feishu.cn/wiki/FhGGwVZSyiCqHCkTVQYcKHQCnKf) | | | 1304 | [cn.etetet.yiuireddot](https://github.com/ET-Packages/cn.etetet.yiuireddot) | [红点](https://lib9kmxvq7k.feishu.cn/wiki/XzyawmryHitNVNk9QVtcDAftn5O) | | | 1305 | [cn.etetet.yiuilocalization](https://github.com/ET-Packages/cn.etetet.yiuilocalization) | [多语言](https://lib9kmxvq7k.feishu.cn/wiki/ZOKxwi5XsijdX8kPU9McSxs1nxd) | | | 1306 | [cn.etetet.yiuieffect](https://github.com/ET-Packages/cn.etetet.yiuieffect) | [UI特效](https://lib9kmxvq7k.feishu.cn/wiki/PA9CwTAMMiBxx9k30iBcXJnznIc) | | | 1801 | cn.etetet.yiuiluban | [Luban 配置](https://lib9kmxvq7k.feishu.cn/wiki/W1ylwC9xDip1YQk4eijcxgO9nh0) |100 | | 1802 | cn.etetet.yiuinumeric | [Numeric 数值系统](https://lib9kmxvq7k.feishu.cn/wiki/GHDOwsmy0iQQMok3gU7cgxbpn7x) |100 | | 1803 | cn.etetet.yiuicondition | [Condition 条件系统](https://lib9kmxvq7k.feishu.cn/wiki/Kc0awA9EXiRJylkreL9cfLJFnsg) |(限免)Luban送 | | 1804 | cn.etetet.yiuigameobjectpool | [GameObjectPool 游戏对象缓存池](https://lib9kmxvq7k.feishu.cn/wiki/UyigweBFXipNJnkCIY6coHzFnSc) |(限免)Luban送 | | 1805 | cn.etetet.yiuiaudio | [Audio 音乐音效](https://lib9kmxvq7k.feishu.cn/wiki/ECbzwVPmDiQNY9ktJHecCuGsnig) |(限免)Luban送 | | 1806 | cn.etetet.yiuivideo | [Video 视频播放](https://lib9kmxvq7k.feishu.cn/wiki/Wt7twM5nciZl5rkKf7ScpGGAn4c) |(限免)Luban送 | | 1807 | cn.etetet.yiuidamagetips | [DamageTips 伤害提示](https://lib9kmxvq7k.feishu.cn/wiki/Yt8PwrobfiYijDkuZTWcsGyJnFh) |(限免)Luban送 | | | 2000-2999 XET | | | | 2000 | [cn.etetet.xetdemo](https://github.com/ET-Packages/cn.etetet.xetdemo) | XET Demo,演示了 cn.etetet.xetfui 的使用方式 | | | 2001 | [cn.etetet.xetfui](https://github.com/ET-Packages/cn.etetet.xetfui) | FairyGUI 代码导出工具 | | | 2002 | [cn.etetet.xetyooassets](https://github.com/ET-Packages/cn.etetet.xetyooassets) | 在 cn.etetet.yooassets 基础上加了一些需要的功能 | | | 3000 | [cn.etetet.configauto](https://github.com/ET-Packages/cn.etetet.configauto) | 直接用C#代码作为配置 | | ================================================ FILE: Book/8.3ET9项目怎么进行包更新.md ================================================ ET9变成了包的形式,但是对用用户来说,区别不大,大家安装好包之后,将项目跟包全部传到自己的git仓库作为master分支, 开发的时候切换出来一个分支dev进行开发。每次要更新,则先切换到master分支进行更新,更新完成后提交到master分支, 再切换到dev分支进行合并master分支,这样就完成了合并 ================================================ FILE: ChangeLog.md ================================================ |包名| 版本 | 更新内容 | |-------|-------|-------| |cn.etetet.core| 2.0.6 | 修复一个SoretedDictionary跟SortedSet只读遍历的时候多线程不安全的问题 | |cn.etetet.core| 1.0.1 | 修复TcpTransport服务端会去Connect客户端的bug | |cn.etetet.excel| 0.0.8| 修复excelexporter的编译bug | |cn.etetet.router| 0.0.6| RouterConfigSingleton删除 | |cn.etetet.hybridclr| 6.7.1| 同步最新版本hybridclr| |cn.etetet.statesync| 0.0.47| 更新每个包的描述| |cn.etetet.statesync| 0.0.46| 增加ai机器人,服务端独立启动,在控制台输入 CreateRobot --Num=10 创建10个机器| ================================================ FILE: Directory.Build.props ================================================ 11.0 0169,0649,3021,8981 Portable true ================================================ FILE: ET.sln.DotSettings ================================================  <root><filter><namespace_mask>Boo</namespace_mask><has_type_parameters>Any</has_type_parameters><element_kind>Any</element_kind></filter></root> True True True <> True False False True DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW SUGGESTION SUGGESTION SUGGESTION SUGGESTION DO_NOT_SHOW SUGGESTION SUGGESTION SUGGESTION DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW <?xml version="1.0" encoding="utf-16"?><Profile name="Unity"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSRemoveCodeRedundancies>True</CSRemoveCodeRedundancies><CSUseVar><BehavourStyle>DISABLED</BehavourStyle><LocalVariableStyle>IMPLICIT_WHEN_INITIALIZER_HAS_TYPE</LocalVariableStyle><ForeachVariableStyle>IMPLICIT_EXCEPT_SIMPLE_TYPES</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" /></Profile> Unity True Required Required Required Required Field, Property, Event, Method False False 2 JOIN ALWAYS_ADD ALWAYS_ADD ALWAYS_ADD ALWAYS_ADD ALWAYS_ADD ALWAYS_ADD NONE 1 1 True False True False True 1 private public protected internal new abstract virtual override sealed static readonly extern unsafe volatile async NEVER ALWAYS ALWAYS ALWAYS False NEVER False NEVER True ALWAYS_USE LINE_BREAK LINE_BREAK True True False False True False False True False True False False False False False WRAP_IF_LONG 150 WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG True True False TwoSteps TwoSteps False False <?xml version="1.0" encoding="utf-8" ?> <!-- I. Overall I.1 Each pattern can have <Match>....</Match> element. For the given type declaration, the pattern with the match, evaluated to 'true' with the largest weight, will be used I.2 Each pattern consists of the sequence of <Entry>...</Entry> elements. Type member declarations are distributed between entries I.3 If pattern has RemoveAllRegions="true" attribute, then all regions will be cleared prior to reordering. Otherwise, only auto-generated regions will be cleared I.4 The contents of each entry is sorted by given keys (First key is primary, next key is secondary, etc). Then the declarations are grouped and en-regioned by given property II. Available match operands Each operand may have Weight="..." attribute. This weight will be added to the match weight if the operand is evaluated to 'true'. The default weight is 1 II.1 Boolean functions: II.1.1 <And>....</And> II.1.2 <Or>....</Or> II.1.3 <Not>....</Not> II.2 Operands II.2.1 <Kind Is="..."/>. Kinds are: class, struct, interface, enum, delegate, type, constructor, destructor, property, indexer, method, operator, field, constant, event, member II.2.2 <Name Is="..." [IgnoreCase="true/false"] />. The 'Is' attribute contains regular expression II.2.3 <HasAttribute CLRName="..." [Inherit="true/false"] />. The 'CLRName' attribute contains regular expression II.2.4 <Access Is="..."/>. The 'Is' values are: public, protected, internal, protected internal, private II.2.5 <Static/> II.2.6 <Abstract/> II.2.7 <Virtual/> II.2.8 <Override/> II.2.9 <Sealed/> II.2.10 <Readonly/> II.2.11 <ImplementsInterface CLRName="..."/>. The 'CLRName' attribute contains regular expression II.2.12 <HandlesEvent /> --> <Patterns xmlns="urn:shemas-jetbrains-com:member-reordering-patterns"> <!--Do not reorder COM interfaces and structs marked by StructLayout attribute--> <Pattern> <Match> <Or Weight="100"> <And> <Kind Is="interface"/> <Or> <HasAttribute CLRName="System.Runtime.InteropServices.InterfaceTypeAttribute"/> <HasAttribute CLRName="System.Runtime.InteropServices.ComImport"/> </Or> </And> <HasAttribute CLRName="System.Runtime.InteropServices.StructLayoutAttribute"/> </Or> </Match> </Pattern> <!--Special formatting of NUnit test fixture--> <Pattern RemoveAllRegions="true"> <Match> <And Weight="100"> <Kind Is="class"/> <HasAttribute CLRName="NUnit.Framework.TestFixtureAttribute" Inherit="true"/> </And> </Match> <!--Setup/Teardow--> <Entry> <Match> <And> <Kind Is="method"/> <Or> <HasAttribute CLRName="NUnit.Framework.SetUpAttribute" Inherit="true"/> <HasAttribute CLRName="NUnit.Framework.TearDownAttribute" Inherit="true"/> <HasAttribute CLRName="NUnit.Framework.FixtureSetUpAttribute" Inherit="true"/> <HasAttribute CLRName="NUnit.Framework.FixtureTearDownAttribute" Inherit="true"/> </Or> </And> </Match> <Group Region="Setup/Teardown"/> </Entry> <!--All other members--> <Entry/> <!--Test methods--> <Entry> <Match> <And Weight="100"> <Kind Is="method"/> <HasAttribute CLRName="NUnit.Framework.TestAttribute" Inherit="false"/> </And> </Match> <Sort> <Name/> </Sort> </Entry> </Pattern> <!--Default pattern--> <Pattern> <!--public delegate--> <Entry> <Match> <And Weight="100"> <Access Is="public"/> <Kind Is="delegate"/> </And> </Match> <Sort> <Name/> </Sort> <Group Region="Delegates"/> </Entry> <!--public enum--> <Entry> <Match> <And Weight="100"> <Access Is="public"/> <Kind Is="enum"/> </And> </Match> <Sort> <Name/> </Sort> <Group> <Name Region="${Name} enum"/> </Group> </Entry> <!--static fields and constants--> <Entry> <Match> <Or> <Kind Is="constant"/> <And> <Kind Is="field"/> <Static/> </And> </Or> </Match> <Sort> <Kind Order="constant field"/> </Sort> </Entry> <!--instance fields--> <Entry> <Match> <And> <Kind Is="field"/> <Not> <Static/> </Not> </And> </Match> <Sort> <Readonly/> <Name/> </Sort> </Entry> <!--Constructors. Place static one first--> <Entry> <Match> <Kind Is="constructor"/> </Match> <Sort> <Static/> </Sort> </Entry> <!--properties, indexers--> <Entry> <Match> <Or> <Kind Is="property"/> <Kind Is="indexer"/> </Or> </Match> </Entry> <!--interface implementations--> <Entry> <Match> <And Weight="100"> <Kind Is="member"/> <ImplementsInterface/> </And> </Match> <Sort> <ImplementsInterface Immediate="true"/> </Sort> <Group> <ImplementsInterface Immediate="true" Region="${ImplementsInterface} Members"/> </Group> </Entry> <!--all other members--> <Entry/> <!--nested types--> <Entry> <Match> <Kind Is="type"/> </Match> <Sort> <Name/> </Sort> <Group> <Name Region="Nested type: ${Name}"/> </Group> </Entry> </Pattern> </Patterns> UseExplicitType UseExplicitType True False False True False True False False True True <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> <Policy Inspect="False" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="False" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="False" Prefix="__" Suffix="" Style="aaBb" /> <Policy Inspect="False" Prefix="__" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="False" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="False" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> $object$_On$event$ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> $object$_On$event$ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> BOTH_SIDES True True ObjectBrowser False C:\Apps\Java\jdk1.8.0_131 C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\Roslyn\csi.exe 79 True False Never Never False Never False True False Dark True True True True True True True True True True True True True True True True True True True True True True True True True True False False VS System.CodeDom.Compiler.GeneratedCodeAttribute <data><AttributeFilter ClassMask="System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute" IsEnabled="True" /><AttributeFilter ClassMask="System.CodeDom.Compiler.GeneratedCodeAttribute" IsEnabled="True" /></data> True True True True CSharpBracesLayoutPage 247,0 True CSharpOtherPage False False False True True True True True False False True False True 269 True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True False C:\Users\USER-PC\AppData\Local\JetBrains\Shared\vAny\Sessions False 366 ================================================ FILE: LICENSE ================================================ ET License 1. 该ET版本版权属于广州市一踢互联网科技有限公司所有。 2. 该ET版本可以任意修改代码,或者在ET基础上添加代码,但是仅限于自己或者自己任职的公司使用,不得传播给他人,私下传播ET修改版或者基于ET开发的代码都是违反ET License的行为 3. 自行修改的代码或者在ET基础上添加的代码,如果要分享给他人使用,必须以Package的形式放到ET的Package仓库中,具体请咨询广州市一踢互联网科技有限公司的唐海,QQ:80081771 4. 该版本可以用于开发任何商业项目。在项目上线运营之前必须主动通知广州市一踢互联网科技有限公司上线项目的名称,上线项目的公司等信息。广州市一踢互联网科技有限公司不会泄露这些信息,仅仅作为统计使用,如果需要公布,则会获得上线项目公司的许可 ================================================ FILE: Packages/.gitignore ================================================ cn.etetet.* ================================================ FILE: Packages/com.etetet.init/.gitignore ================================================ obj/ /**/AssemblyReference.asmref /**/AssemblyReference.asmref.meta ================================================ FILE: Packages/com.etetet.init/Editor/ET.Init.Editor.asmdef ================================================ { "name": "ET.Init.Editor", "rootNamespace": "", "references": [ "ET.Init" ], "includePlatforms": [ "Editor" ], "excludePlatforms": [], "allowUnsafeCode": false, "overrideReferences": false, "precompiledReferences": [], "autoReferenced": true, "defineConstraints": [], "versionDefines": [], "noEngineReferences": false } ================================================ FILE: Packages/com.etetet.init/Editor/ET.Init.Editor.asmdef.meta ================================================ fileFormatVersion: 2 guid: 255f768bf23bb4542a2a2f6defebc2e3 AssemblyDefinitionImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.etetet.init/Editor/GitDependencyResolver/DependencyResolver.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using ET; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Attributes; using UnityEditor; using UnityEditor.PackageManager; using PackageInfo = UnityEditor.PackageManager.PackageInfo; using UnityEditor.PackageManager.Requests; using Debug = UnityEngine.Debug; namespace Hibzz.DependencyResolver { [InitializeOnLoad] public class DependencyResolver { //[MenuItem("ET/MoveToPackage")] static void MoveToPackage(string package, string version) { string packageName = default; string moveFileName = default; #if UNITY_6000_0_OR_NEWER packageName = package; moveFileName = "MoveToPackages_6"; #else packageName =$"{package}@{version}"; moveFileName = "MoveToPackages"; #endif string dir = Path.Combine("Library/PackageCache", packageName); if (!Directory.Exists(dir)) { return; } Debug.Log($"move package: {packageName}"); Process process = ProcessHelper.PowerShell($"-NoExit -ExecutionPolicy Bypass -File ./Packages/com.etetet.init/{moveFileName}.ps1 {package} {version}", waitExit: true); Debug.Log(process.StandardOutput.ReadToEnd()); } static DependencyResolver() { Events.registeredPackages += OnPackagesRegistered; } // Invoked when the package manager completes registering new packages static void OnPackagesRegistered(PackageRegistrationEventArgs packageRegistrationInfo) { if (packageRegistrationInfo.added.Count == 0 && packageRegistrationInfo.changedFrom.Count == 0) { return; } Debug.Log($"Packages Registered: {string.Join(" ", packageRegistrationInfo.added.Select(x=>x.name))}"); // loop through all of the added packages and get their git // dependencies and add it to the list that contains all the // dependencies that need to be installed foreach (var package in packageRegistrationInfo.added) { if (!package.name.StartsWith("cn.etetet.")) { continue; } MoveToPackage(package.name, package.version); } foreach (var package in packageRegistrationInfo.changedFrom) { if (!package.name.StartsWith("cn.etetet.")) { continue; } MoveToPackage(package.name, package.version); } AssetDatabase.Refresh(); } [MenuItem("ET/Init/RepairDependencies")] static void RepairDependencies() { foreach (var directory in Directory.GetDirectories("Library/PackageCache", "cn.etetet.*")) { string baseName = Path.GetFileName(directory); if (!baseName.StartsWith("cn.etetet.")) { continue; } string[] ss = baseName.Split("@"); string packageName = ss[0]; #if UNITY_6000_0_OR_NEWER string version = ""; #else string version = ss[1]; #endif MoveToPackage(packageName, version); } AssetDatabase.Refresh(); Debug.Log($"repaire package finish"); } } } ================================================ FILE: Packages/com.etetet.init/Editor/GitDependencyResolver/DependencyResolver.cs.meta ================================================ fileFormatVersion: 2 guid: d087a58f7e93a0b449fdce47cfbd701b MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.etetet.init/Editor/GitDependencyResolver.meta ================================================ fileFormatVersion: 2 guid: ce7769db96264d48961e70d63f9d52ab timeCreated: 1717601777 ================================================ FILE: Packages/com.etetet.init/Editor/PackageGit.cs ================================================ using System; using System.Collections.Generic; using System.IO; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Attributes; namespace ET { [BsonIgnoreExtraElements] public class PackageGit { public int Id; public string Name; public Dictionary GitDependencies; public Dictionary ScriptsReferences; } public static class PackageGitHelper { public static PackageGit Load(string packageJsonPath) { if (!File.Exists(packageJsonPath)) { throw new Exception($"not found packagegit.json: {packageJsonPath}, retry refresh unity!"); } string packageJsonContent = File.ReadAllText(packageJsonPath); PackageGit packageGit = BsonSerializer.Deserialize(packageJsonContent); return packageGit; } } } ================================================ FILE: Packages/com.etetet.init/Editor/PackageGit.cs.meta ================================================ fileFormatVersion: 2 guid: b809ee54965a437f991cddc40f76e4da timeCreated: 1720094387 ================================================ FILE: Packages/com.etetet.init/Editor/ProcessHelper.cs ================================================ using System; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading.Tasks; using Debug = UnityEngine.Debug; using Path = System.IO.Path; namespace ET { internal static class ProcessHelper { public static System.Diagnostics.Process PowerShell(string arguments, string workingDirectory = ".", bool waitExit = false) { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { return Run("powershell.exe", arguments, workingDirectory, waitExit); } else { return Run("/usr/local/bin/pwsh", arguments, workingDirectory, waitExit); } } public static System.Diagnostics.Process Run(string exe, string arguments, string workingDirectory = ".", bool waitExit = false) { //Log.Debug($"Process Run exe:{exe} ,arguments:{arguments} ,workingDirectory:{workingDirectory}"); try { bool redirectStandardOutput = false; bool redirectStandardError = false; bool useShellExecute = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); if (waitExit) { redirectStandardOutput = true; redirectStandardError = true; useShellExecute = false; } ProcessStartInfo info = new ProcessStartInfo { FileName = exe, Arguments = arguments, CreateNoWindow = true, UseShellExecute = useShellExecute, WorkingDirectory = workingDirectory, RedirectStandardOutput = redirectStandardOutput, RedirectStandardError = redirectStandardError, }; System.Diagnostics.Process process = System.Diagnostics.Process.Start(info); if (waitExit) { process.WaitForExit(); } return process; } catch (Exception e) { throw new Exception($"dir: {Path.GetFullPath(workingDirectory)}, command: {exe} {arguments}", e); } } } } ================================================ FILE: Packages/com.etetet.init/Editor/ProcessHelper.cs.meta ================================================ fileFormatVersion: 2 guid: a193dc186ced416791dd3aa22dc2c2c7 timeCreated: 1720432378 ================================================ FILE: Packages/com.etetet.init/Editor.meta ================================================ fileFormatVersion: 2 guid: bbb18281570c7404da25cd964799c89d folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.etetet.init/MoveToPackages.ps1 ================================================ param($packageName, $version) Write-Host $packageName, $version $from = "Library/PackageCache/$packageName@" + $version Move-Item $from "Packages/$packageName" Write-Host "move finish!" $packageName $version #Read-Host -Prompt "Press Enter to exit" ================================================ FILE: Packages/com.etetet.init/MoveToPackages.ps1.meta ================================================ fileFormatVersion: 2 guid: 202ab9d4b020c1f4bad8937d2497fba6 DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.etetet.init/MoveToPackages_6.ps1 ================================================ param($packageName, $version) Write-Host $packageName $from = "Library/PackageCache/$packageName" Move-Item $from "Packages/$packageName" Write-Host "move finish!" $packageName #Read-Host -Prompt "Press Enter to exit" ================================================ FILE: Packages/com.etetet.init/MoveToPackages_6.ps1.meta ================================================ fileFormatVersion: 2 guid: 6997902c080851b4586429ad0eb5197b DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.etetet.init/Plugins/MongoDB.meta ================================================ fileFormatVersion: 2 guid: d7e6591a3956b6742880c17ccb9ee588 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.etetet.init/Plugins.meta ================================================ fileFormatVersion: 2 guid: cfc13919d8b071d458f99014dca6462c folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.etetet.init/package.json ================================================ { "name": "com.etetet.init", "displayName": "ET.Init", "version": "0.0.1", "unity": "2022.3", "description": "et init", "author": { "name": "tanghai", "url": "https://github.com/egametang/ET" }, "repository": { "type": "git", "url": "https://github.com/egametang/ET" }, "relatedPackages": {} } ================================================ FILE: Packages/com.etetet.init/package.json.meta ================================================ fileFormatVersion: 2 guid: cc4817c723bbe5542aab7d1ee4e11cc9 PackageManifestImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/CHANGELOG.md ================================================ # ChangeLog ## 0.0.1 - 2020-05-11 - Package created ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/CHANGELOG.md.meta ================================================ fileFormatVersion: 2 guid: 3e6d9b2004fec8de1adc49c18ba1bdcb TextScriptImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/Core/CredentialManager.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using Tomlyn; using Tomlyn.Model; using Tomlyn.Syntax; using UnityEngine; namespace Halodi.PackageRegistry.Core { public class NPMCredential { public string url; public string token; public bool alwaysAuth; } public class CredentialManager { private string upmconfigFile = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".upmconfig.toml"); private List credentials = new List(); public List CredentialSet { get { return credentials; } } public String[] Registries { get { String[] urls = new String[credentials.Count]; int index = 0; foreach (NPMCredential cred in CredentialSet) { urls[index] = cred.url; ++index; } return urls; } } public CredentialManager() { if (File.Exists(upmconfigFile)) { var upmconfig = Toml.Parse(File.ReadAllText(upmconfigFile)); if (upmconfig.HasErrors) { Debug.LogError("Cannot load upmconfig, invalid format"); return; } TomlTable table = upmconfig.ToModel(); if(table != null && table.ContainsKey("npmAuth")) { TomlTable auth = (TomlTable)table["npmAuth"]; if (auth != null) { foreach (var registry in auth) { NPMCredential cred = new NPMCredential(); cred.url = registry.Key; TomlTable value = (TomlTable)registry.Value; cred.token = (string)value["token"]; cred.alwaysAuth = (bool)value["alwaysAuth"]; credentials.Add(cred); } } } } } public void Write() { var doc = new DocumentSyntax(); foreach (var credential in credentials) { if (string.IsNullOrEmpty(credential.token)) { credential.token = ""; } doc.Tables.Add(new TableSyntax(new KeySyntax("npmAuth", credential.url)) { Items = { {"token", credential.token}, {"alwaysAuth", credential.alwaysAuth} } }); } File.WriteAllText(upmconfigFile, doc.ToString()); } public bool HasRegistry(string url) { return credentials.Any(x => x.url.Equals(url, StringComparison.Ordinal)); } public NPMCredential GetCredential(string url) { return credentials.FirstOrDefault(x => x.url?.Equals(url, StringComparison.Ordinal) ?? false); } public void SetCredential(string url, bool alwaysAuth, string token) { if (HasRegistry(url)) { var cred = GetCredential(url); cred.url = url; cred.alwaysAuth = alwaysAuth; cred.token = token; } else { NPMCredential newCred = new NPMCredential(); newCred.url = url; newCred.alwaysAuth = alwaysAuth; newCred.token = token; credentials.Add(newCred); } } public void RemoveCredential(string url) { if (HasRegistry(url)) { credentials.RemoveAll(x => x.url.Equals(url, StringComparison.Ordinal)); } } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/Core/CredentialManager.cs.meta ================================================ fileFormatVersion: 2 guid: d007b6fca56974d8f856529be2d2130a MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/Core/RegistryManager.cs ================================================ using System; using System.Collections.Generic; using System.IO; using Newtonsoft.Json.Linq; using UnityEditor; using UnityEngine; namespace Halodi.PackageRegistry.Core { public class RegistryManager { private string manifest = Path.Combine(Application.dataPath, "..", "Packages", "manifest.json"); public List registries { get; private set; } public CredentialManager credentialManager { get; private set; } public RegistryManager() { this.credentialManager = new CredentialManager(); this.registries = new List(); JObject manifestJSON = JObject.Parse(File.ReadAllText(manifest)); JArray Jregistries = (JArray)manifestJSON["scopedRegistries"]; if (Jregistries != null) { foreach (var JRegistry in Jregistries) { registries.Add(LoadRegistry((JObject)JRegistry)); } } else { Debug.Log("No scoped registries set"); } } private ScopedRegistry LoadRegistry(JObject Jregistry) { ScopedRegistry registry = new ScopedRegistry(); registry.name = (string)Jregistry["name"]; registry.url = (string)Jregistry["url"]; List scopes = new List(); foreach (var scope in (JArray)Jregistry["scopes"]) { scopes.Add((string)scope); } registry.scopes = new List(scopes); if (credentialManager.HasRegistry(registry.url)) { NPMCredential credential = credentialManager.GetCredential(registry.url); registry.auth = credential.alwaysAuth; registry.token = credential.token; } return registry; } private void UpdateScope(ScopedRegistry registry, JToken registryElement) { JArray scopes = new JArray(); foreach (var scope in registry.scopes) { scopes.Add(scope); } registryElement["scopes"] = scopes; } private JToken GetOrCreateScopedRegistry(ScopedRegistry registry, JObject manifestJSON) { JArray Jregistries = (JArray)manifestJSON["scopedRegistries"]; if (Jregistries == null) { Jregistries = new JArray(); manifestJSON["scopedRegistries"] = Jregistries; } foreach (var JRegistryElement in Jregistries) { if (JRegistryElement["name"] != null && JRegistryElement["url"] != null && String.Equals(JRegistryElement["name"].Value(), registry.name, StringComparison.Ordinal) && String.Equals(JRegistryElement["url" ].Value(), registry.url, StringComparison.Ordinal)) { UpdateScope(registry, JRegistryElement); return JRegistryElement; } } JObject JRegistry = new JObject(); JRegistry["name"] = registry.name; JRegistry["url"] = registry.url; UpdateScope(registry, JRegistry); Jregistries.Add(JRegistry); return JRegistry; } public void Remove(ScopedRegistry registry) { JObject manifestJSON = JObject.Parse(File.ReadAllText(manifest)); JArray Jregistries = (JArray)manifestJSON["scopedRegistries"]; foreach (var JRegistryElement in Jregistries) { if (JRegistryElement["name"] != null && JRegistryElement["url"] != null && JRegistryElement["name"].Value().Equals(registry.name, StringComparison.Ordinal) && JRegistryElement["url" ].Value().Equals(registry.url, StringComparison.Ordinal)) { JRegistryElement.Remove(); break; } } write(manifestJSON); } public void Save(ScopedRegistry registry) { JObject manifestJSON = JObject.Parse(File.ReadAllText(manifest)); JToken manifestRegistry = GetOrCreateScopedRegistry(registry, manifestJSON); if(!string.IsNullOrEmpty(registry.token)) { credentialManager.SetCredential(registry.url, registry.auth, registry.token); } else { credentialManager.RemoveCredential(registry.url); } write(manifestJSON); credentialManager.Write(); } private void write(JObject manifestJSON) { File.WriteAllText(manifest, manifestJSON.ToString()); AssetDatabase.Refresh(); } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/Core/RegistryManager.cs.meta ================================================ fileFormatVersion: 2 guid: ed727f03f93e90392bda8e4663336ae2 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/Core/ScopedRegistry.cs ================================================ using System; using System.Collections.Generic; using UnityEngine; namespace Halodi.PackageRegistry.Core { [System.Serializable] public class ScopedRegistry { public string name; public string url; public List scopes = new List(); public bool auth; public string token; public override string ToString() { return JsonUtility.ToJson(this, true); } public bool isValidCredential() { if( string.IsNullOrEmpty(url) || !Uri.IsWellFormedUriString(url, UriKind.Absolute)) { return false; } if(auth) { if(string.IsNullOrEmpty(token)) { return false; } } return true; } public bool isValid() { if(string.IsNullOrEmpty(name)) { return false; } if(scopes.Count < 1) { return false; } scopes.RemoveAll(string.IsNullOrEmpty); foreach(string scope in scopes) { if(Uri.CheckHostName(scope) != UriHostNameType.Dns) { Debug.LogWarning("Invalid scope " + scope); return false; } } return isValidCredential(); } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/Core/ScopedRegistry.cs.meta ================================================ fileFormatVersion: 2 guid: be826e7a3923f2b79b58b8a5d923ba54 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/Core/UpgradePackagesManager.cs ================================================ using UnityEngine; using UnityEditor; using UnityEditor.PackageManager.Requests; using UnityEditor.PackageManager; using System.Threading; using System.Collections.Generic; using Artees.UnitySemVer; using System; namespace Halodi.PackageRegistry.Core { public class UpgradePackagesManager { public class PackageUpgradeState { public PackageUpgradeState(UnityEditor.PackageManager.PackageInfo info) { this.info = info; previewAvailable = false; stableAvailable = false; verifiedAvailable = false; hasVerified = false; stableVersion = SemVer.Parse(info.version); previewVersion = SemVer.Parse(info.version); try { current = SemVer.Parse(info.version); } catch { Debug.LogError("Cannot parse version for package " + info.displayName + ": " + info.version); } if (info.source == PackageSource.Git) { previewAvailable = true; preview = info.packageId; stableAvailable = true; stable = info.packageId; } else if (info.source == PackageSource.Registry) { string[] compatible = info.versions.compatible; foreach (string ver in compatible) { try { SemVer version = SemVer.Parse(ver); if (string.IsNullOrWhiteSpace(version.preRelease)) { if (version > stableVersion) { stableVersion = version; stableAvailable = true; stable = info.name + "@" + ver; } } else { // This is a pre-release if (version > previewVersion) { previewVersion = version; previewAvailable = true; preview = info.name + "@" + ver; } } } catch { Debug.LogError("Invalid version for package " + info.displayName + ": " + ver); } } #if UNITY_2022_2_OR_NEWER string verified = info.versions.recommended; #else string verified = info.versions.verified; #endif hasVerified = !String.IsNullOrWhiteSpace(verified); if(hasVerified) { try { verifiedVersion = SemVer.Parse(verified); if(verifiedVersion > current) { verifiedAvailable = verifiedVersion > current; verified = info.name + "@" + verified; } } catch { Debug.LogError("Cannot parse version for package " + info.displayName + ": " + verified); } } } } internal string GetCurrentVersion() { return info.packageId; } public UnityEditor.PackageManager.PackageInfo info; private SemVer current; private bool previewAvailable; private SemVer previewVersion; private string preview; private bool stableAvailable; private SemVer stableVersion; private string stable; private bool hasVerified; private bool verifiedAvailable; private SemVer verifiedVersion; private string verified; public bool HasNewVersion(bool showPreviewVersion, bool useVerified) { if(useVerified && hasVerified) { return verifiedAvailable; } else if (showPreviewVersion) { return previewAvailable || stableAvailable; } else { return stableAvailable; } } public string GetNewestVersion(bool showPreviewVersion, bool useVerified) { if(useVerified && hasVerified) { if(verifiedAvailable) { return verified; } } else if (showPreviewVersion) { if (previewAvailable) { if (!stableAvailable || previewVersion > stableVersion) { return preview; } } } if (stableAvailable) { if (stableAvailable) { return stable; } } return null; } } public List UpgradeablePackages = new List(); private ListRequest request; public bool packagesLoaded = false; public UpgradePackagesManager() { #if UNITY_2019_1_OR_NEWER request = Client.List(false, false); #else request = Client.List(); #endif } public void Update() { if (!packagesLoaded && request.IsCompleted) { if (request.Status == StatusCode.Success) { PackageCollection collection = request.Result; foreach (UnityEditor.PackageManager.PackageInfo info in collection) { UpgradeablePackages.Add(new PackageUpgradeState(info)); } } else { Debug.LogError("Cannot query package manager for packages"); } packagesLoaded = true; } } public bool UpgradePackage(String packageWithVersion, ref string error) { AddRequest request = UnityEditor.PackageManager.Client.Add(packageWithVersion); while (!request.IsCompleted) { Thread.Sleep(100); } if (request.Status == StatusCode.Success) { return true; } else { error = request.Error.message; return false; } } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/Core/UpgradePackagesManager.cs.meta ================================================ fileFormatVersion: 2 guid: 62211760241e94052a18720a8ae0a8ff MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/Core.meta ================================================ fileFormatVersion: 2 guid: 4cf3f1318b46e144cbe43e9daf69c5b5 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/NPM/NPMLogin.cs ================================================ using System; using System.IO; using System.Net; using System.Text; using UnityEngine; using Random = System.Random; namespace Halodi.PackageRegistry.NPM { [System.Serializable] internal class NPMLoginRequest { public string name; public string password; } public class ExpectContinueAware : System.Net.WebClient { protected override System.Net.WebRequest GetWebRequest(Uri address) { System.Net.WebRequest request = base.GetWebRequest(address); if (request is System.Net.HttpWebRequest) { var hwr = request as System.Net.HttpWebRequest; hwr.ServicePoint.Expect100Continue = false; hwr.AllowAutoRedirect = false; } return request; } } public class NPMLogin { internal static string UrlCombine(string start, string more) { if (string.IsNullOrEmpty(start)) { return more; } else if (string.IsNullOrEmpty(more)) { return start; } return start.TrimEnd('/') + "/" + more.TrimStart('/'); } public static string GetBintrayToken(string user, string apiKey) { return Convert.ToBase64String(Encoding.ASCII.GetBytes(user + ":" + apiKey)); } public static NPMResponse GetLoginToken(string url, string user, string password) { using (var client = new WebClient()) { string loginUri = UrlCombine(url, "/-/user/org.couchdb.user:" + user); client.Headers.Add(HttpRequestHeader.Accept, "application/json"); client.Headers.Add(HttpRequestHeader.ContentType, "application/json"); client.Headers.Add(HttpRequestHeader.Authorization, "Basic " + Convert.ToBase64String(Encoding.ASCII.GetBytes(user + ":" + password))); NPMLoginRequest request = new NPMLoginRequest(); request.name = user; request.password = password; string requestString = JsonUtility.ToJson(request); try { string responseString = client.UploadString(loginUri, WebRequestMethods.Http.Put, requestString); NPMResponse response = JsonUtility.FromJson(responseString); return response; } catch (WebException e) { NPMResponse response = new NPMResponse(); response.error = WebExceptionParser.ParseWebException(e); return response; } } } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/NPM/NPMLogin.cs.meta ================================================ fileFormatVersion: 2 guid: b48148742aea9c373b5a101a0794ff57 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/NPM/NPMPublish.cs ================================================ using System; using System.IO; using System.Net; using System.Text; using Halodi.PackageRegistry.Core; using UnityEngine; namespace Halodi.PackageRegistry.NPM { public class NPMPublish { public static void Publish(string packageFolder, string registry) { CredentialManager manager = new CredentialManager(); if (!manager.HasRegistry(registry)) { throw new System.IO.IOException("Credentials not set for registry " + registry); } string token = manager.GetCredential(registry).token; PublicationManifest manifest = new PublicationManifest(packageFolder, registry); ; using (var client = new ExpectContinueAware()) { string upload = NPMLogin.UrlCombine(registry, manifest.name); client.Encoding = Encoding.UTF8; client.Headers.Add(HttpRequestHeader.Accept, "application/json"); client.Headers.Add(HttpRequestHeader.ContentType, "application/json"); client.Headers.Add(HttpRequestHeader.Authorization, "Bearer " + token); // Headers set by the NPM client, but not by us. Option to try with compatibility issues. // client.Headers.Add("npm-in-ci", "false"); // client.Headers.Add("npm-scope", ""); // client.Headers.Add(HttpRequestHeader.UserAgent, "npm/6.14.4 node/v12.16.2 linux x64"); // var random = new Random(); // string a = String.Format("{0:X8}", random.Next(0x10000000, int.MaxValue)).ToLower(); // string b = String.Format("{0:X8}", random.Next(0x10000000, int.MaxValue)).ToLower(); // client.Headers.Add("npm-session", a + b); // client.Headers.Add("referer", "publish"); try { string responseString = client.UploadString(upload, WebRequestMethods.Http.Put, manifest.Request); try { NPMResponse response = JsonUtility.FromJson(responseString); if (!response.success && string.IsNullOrEmpty(response.ok)) { throw new System.IO.IOException(responseString); } } catch (Exception) { throw new System.IO.IOException(responseString); } } catch (WebException e) { throw new System.IO.IOException(WebExceptionParser.ParseWebException(e)); } } } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/NPM/NPMPublish.cs.meta ================================================ fileFormatVersion: 2 guid: 4c14ed213814c5c26835ad084ee31ef0 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/NPM/NPMResponse.cs ================================================ namespace Halodi.PackageRegistry.NPM { [System.Serializable] public class NPMResponse { public string error; public string ok; public string token; public bool success; public string reason; } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/NPM/NPMResponse.cs.meta ================================================ fileFormatVersion: 2 guid: e1686b71fd1b8d8ad91a35d5a7994008 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/NPM/PackageTarball.cs ================================================ using Newtonsoft.Json.Linq; using System.IO; using Unity.SharpZipLib.GZip; using Unity.SharpZipLib.Tar; namespace Halodi.PackageRegistry.NPM { /// /// Tool to create tarballs for packages. /// UnityEditor.PackageManager.Client.Pack() creates a broken tarball that gets rejected by bintray. /// public class PackageTarball { public static string Create(string packageFolder, string outputFolder) { JObject manifest = PublicationManifest.LoadManifest(packageFolder); string packageName = manifest["name"] + "-" + manifest["version"] + ".tgz"; Directory.CreateDirectory(outputFolder); string outputFile = Path.Combine(outputFolder, packageName); Stream outStream = File.Create(outputFile); Stream gzoStream = new GZipOutputStream(outStream); TarArchive tarArchive = TarArchive.CreateOutputTarArchive(gzoStream); AddDirectoryFilesToTar(tarArchive, packageFolder, true, "packages/"); tarArchive.Close(); gzoStream.Close(); outStream.Close(); return outputFile; } private static string AppendDirectorySeparatorChar(string path) { // Append a slash only if the path is a directory and does not have a slash. if (Directory.Exists(path) && !path.EndsWith(Path.DirectorySeparatorChar.ToString())) { return path + Path.DirectorySeparatorChar; } return path; } private static void AddDirectoryFilesToTar(TarArchive tarArchive, string sourceDirectory, bool recurse, string directoryName) { // Optionally, write an entry for the directory itself. // Specify false for recursion here if we will add the directory's files individually. TarEntry tarEntry = TarEntry.CreateEntryFromFile(sourceDirectory); tarEntry.Name = directoryName; tarArchive.WriteEntry(tarEntry, false); // Write each file to the tar. string[] filenames = Directory.GetFiles(sourceDirectory); foreach (string filename in filenames) { TarEntry fileEntry = TarEntry.CreateEntryFromFile(filename); fileEntry.Name = directoryName + Path.GetFileName(filename); tarArchive.WriteEntry(fileEntry, true); } if (recurse) { string[] directories = Directory.GetDirectories(sourceDirectory); foreach (string directory in directories) { string dirname = new DirectoryInfo(directory).Name; if (dirname == ".git") { continue; } string newDirectory = directoryName + dirname + "/"; AddDirectoryFilesToTar(tarArchive, directory, recurse, newDirectory); } } } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/NPM/PackageTarball.cs.meta ================================================ fileFormatVersion: 2 guid: 65eda3cbab9699e01aee646d705bf241 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/NPM/PublicationManifest.cs ================================================ using System; using System.IO; using System.Security.Cryptography; using System.Threading; using Newtonsoft.Json.Linq; using UnityEditor; using UnityEditor.PackageManager; using UnityEditor.PackageManager.Requests; using UnityEngine; using System.Linq; using Newtonsoft.Json; using System.Text.RegularExpressions; using Halodi.PackageRegistry.Core; namespace Halodi.PackageRegistry.NPM { /// /// Helper class to create the JSON data to upload to the package server /// internal class PublicationManifest { private JObject j = new JObject(); public string name { get; private set; } private string base64Data; private long size; private string sha512; private string sha1; public string Request { get { return j.ToString(Formatting.None); } } internal static JObject LoadManifest(string packageFolder) { string manifestPath = Path.Combine(packageFolder, "package.json"); if (!File.Exists(manifestPath)) { throw new System.IO.IOException("Invalid package folder. Cannot find package.json in " + packageFolder); } JObject manifest = JObject.Parse(File.ReadAllText(manifestPath)); if (manifest["name"] == null) { throw new System.IO.IOException("Package name not set"); } if (manifest["version"] == null) { throw new System.IO.IOException("Package version not set"); } if (manifest["description"] == null) { throw new System.IO.IOException("Package description not set"); } return manifest; } internal PublicationManifest(string packageFolder, string registry) { CreateTarball(packageFolder); JObject manifest = LoadManifest(packageFolder); name = manifest["name"].ToString(); string version = manifest["version"].ToString(); string description = manifest["description"].ToString(); string tarballName = name + "-" + version + ".tgz"; string tarballPath = name + "/-/" + tarballName; string tarballUri = NPMLogin.UrlCombine(registry, tarballPath); tarballUri = Regex.Replace(tarballUri, @"^https:\/\/", "http://"); string readmeFile = GetReadmeFilename(packageFolder); string readme = null; if(readmeFile != null) { readme = GetReadme(readmeFile); } j["_id"] = name; j["name"] = name; j["description"] = description; j["dist-tags"] = new JObject(); j["dist-tags"]["latest"] = version; j["versions"] = new JObject(); j["versions"][version] = manifest; if (!string.IsNullOrEmpty(readmeFile)) { j["versions"][version]["readme"] = readme; j["versions"][version]["readmeFilename"] = readmeFile; } j["versions"][version]["_id"] = name + "@" + version; // Extra options set by the NPM client. Will not set here as they do not seem neccessary. // j["versions"][version]["_npmUser"] = new JObject(); // j["versions"][version]["_npmUser"]["name"] = ""; // j["versions"][version]["_npmUser"]["email"] = ""; // j["versions"][version]["_npmVersion"] = "6.14.4"; // j["versions"][version]["_nodeVersion"] = "12.16.2"; j["versions"][version]["dist"] = new JObject(); j["versions"][version]["dist"]["integrity"] = sha512; j["versions"][version]["dist"]["shasum"] = sha1; j["versions"][version]["dist"]["tarball"] = tarballUri.ToString(); if (!string.IsNullOrEmpty(readme)) { j["readme"] = readme; } j["_attachments"] = new JObject(); j["_attachments"][tarballName] = new JObject(); j["_attachments"][tarballName]["content_type"] = "application/octet-stream"; j["_attachments"][tarballName]["length"] = new JValue(size); j["_attachments"][tarballName]["data"] = base64Data; } private string GetReadmeFilename(string packageFolder) { foreach (var path in Directory.EnumerateFiles(packageFolder)) { string file = Path.GetFileName(path); if (file.Equals("readme.md", StringComparison.InvariantCultureIgnoreCase) || file.Equals("readme.txt", StringComparison.InvariantCultureIgnoreCase) || file.Equals("readme", StringComparison.InvariantCultureIgnoreCase)) { return path; } } return null; } private string GetReadme(string readmeFile) { return File.ReadAllText(readmeFile); } private string SHA512(byte[] data) { var sha = new SHA512Managed(); byte[] checksum = sha.ComputeHash(data); return "sha512-" + Convert.ToBase64String(checksum); } private string SHA1(byte[] data) { var sha = new SHA1Managed(); byte[] checksum = sha.ComputeHash(data); return BitConverter.ToString(checksum).Replace("-", string.Empty).ToLower(); } public void CreateTarball(string packageFolder) { string folder = FileUtil.GetUniqueTempPathInProject(); string file = PackageTarball.Create(packageFolder, folder); Byte[] bytes = File.ReadAllBytes(file); base64Data = Convert.ToBase64String(bytes); size = bytes.Length; sha1 = SHA1(bytes); sha512 = SHA512(bytes); File.Delete(file); Directory.Delete(folder); } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/NPM/PublicationManifest.cs.meta ================================================ fileFormatVersion: 2 guid: 0c6809e15491b0062ad2e9c9aa05a72a MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/NPM/WebExceptionParser.cs ================================================ using System.Net; namespace Halodi.PackageRegistry.NPM { public class WebExceptionParser { public static string ParseWebException(WebException e) { if (e.Status == WebExceptionStatus.ProtocolError) { HttpWebResponse response = (HttpWebResponse)e.Response; switch (response.StatusCode) { case HttpStatusCode.Unauthorized: return response.StatusCode + ": Invalid credentials."; case HttpStatusCode.BadRequest: case HttpStatusCode.Conflict: return response.StatusCode + ": Check if version already exists on server."; default: return response.StatusCode + ": Unknown error. Try again."; } } else { if (e.InnerException != null) { return e.InnerException.Message; } else { return e.Message; } } } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/NPM/WebExceptionParser.cs.meta ================================================ fileFormatVersion: 2 guid: 38840c461d1475a36bb61118358e2b51 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/NPM.meta ================================================ fileFormatVersion: 2 guid: 2bdc72b5107d76fc9b0eefd914b165d1 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/UI/BulkAddPackages.cs ================================================ using System; using System.IO; using System.Threading; using UnityEditor; using UnityEditor.PackageManager; using UnityEditor.PackageManager.Requests; using UnityEngine; namespace Halodi.PackageRegistry.UI { public class BulkAddPackages : EditorWindow { private string PackageList = ""; //[MenuItem("Packages/Add packages (bulk)", false, 22)] internal static void ManageRegistries() { EditorWindow.GetWindow(true, "Add packages", true); } void OnEnable() { PackageList = ""; minSize = new Vector2(640, 320); } void OnGUI() { EditorGUILayout.LabelField("Add Packages", EditorStyles.whiteLargeLabel); EditorGUILayout.Separator(); PackageList = EditorGUILayout.TextArea(PackageList, GUILayout.Height(200)); EditorGUILayout.LabelField("Add multiple packages. Place each package on a newline."); EditorGUILayout.LabelField("Format:."); EditorGUILayout.LabelField("\tLatest version of package: com.halodi.halodi-unity-package-registry-manager"); EditorGUILayout.LabelField("\tSpecific version: com.halodi.halodi-unity-package-registry-manager@0.1.0"); EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("Add packages")) { AddPackages(); CloseWindow(); } if (GUILayout.Button("Close")) { CloseWindow(); } EditorGUILayout.EndHorizontal(); } private void AddPackages() { string result = ""; bool hasPackages = false; using (StringReader reader = new StringReader(PackageList)) { string line = string.Empty; while ( (line = reader.ReadLine()) != null) { if(!string.IsNullOrEmpty(line)) { AddRequest request = UnityEditor.PackageManager.Client.Add(line); while(!request.IsCompleted) { Thread.Sleep(100); } if(request.Status == StatusCode.Success) { result += "Imported: " + line + Environment.NewLine; } else { result += "Cannot import " + line + ": " + request.Error.message + Environment.NewLine; } hasPackages = true; } } } if(hasPackages) { EditorUtility.DisplayDialog("Added packages", "Packages added:" + Environment.NewLine + Environment.NewLine + result, "OK"); } else { EditorUtility.DisplayDialog("No packages entered", "No packages entered.", "OK"); } } private void CloseWindow() { Close(); GUIUtility.ExitGUI(); } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/UI/BulkAddPackages.cs.meta ================================================ fileFormatVersion: 2 guid: 392cfa6b2194898f5a5328604b1e4814 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/UI/CredentialEditorView.cs ================================================ using System; using Halodi.PackageRegistry.Core; using UnityEditor; using UnityEngine; namespace Halodi.PackageRegistry.UI { class CredentialEditorView : EditorWindow { private bool initialized = false; private CredentialManager credentialManager; private bool createNew; private ScopedRegistry registry; private int tokenMethod; void OnEnable() { tokenMethod = 0; minSize = new Vector2(480, 320); } void OnDisable() { initialized = false; } public void CreateNew(CredentialManager credentialManager) { this.credentialManager = credentialManager; this.createNew = true; this.registry = new ScopedRegistry(); this.initialized = true; } public void Edit(NPMCredential credential, CredentialManager credentialManager) { this.credentialManager = credentialManager; this.registry = new ScopedRegistry(); this.registry.url = credential.url; this.registry.auth = credential.alwaysAuth; this.registry.token = credential.token; this.createNew = false; this.initialized = true; } void OnGUI() { if (initialized) { if (createNew) { EditorGUILayout.LabelField("Add credential", EditorStyles.whiteLargeLabel); registry.url = EditorGUILayout.TextField("Registry URL", registry.url); } else { EditorGUILayout.LabelField("Edit credential", EditorStyles.whiteLargeLabel); EditorGUILayout.LabelField("Registry URL: " + registry.url); } if(string.IsNullOrEmpty(registry.url)) { EditorGUILayout.HelpBox("Enter the registry URL you want to add authentication for.", MessageType.Warning); } registry.auth = EditorGUILayout.Toggle("Always auth", registry.auth); registry.token = EditorGUILayout.TextField("Token", registry.token); EditorGUILayout.Space(); EditorGUI.BeginDisabledGroup(string.IsNullOrEmpty(registry.url)); tokenMethod = GetTokenView.CreateGUI(tokenMethod, registry); if (!string.IsNullOrEmpty(registry.url) && string.IsNullOrEmpty(registry.token)) { EditorGUILayout.HelpBox("Select an authentication method and click on \"Get token\"", MessageType.Warning); } EditorGUI.BeginDisabledGroup(string.IsNullOrEmpty(registry.token)); EditorGUILayout.Space(); EditorGUILayout.BeginVertical(GUILayout.ExpandHeight(true)); EditorGUILayout.EndVertical(); EditorGUILayout.HelpBox("Restart Unity to reload credentials after saving.", MessageType.Info); EditorGUILayout.BeginHorizontal(); if (createNew) { if (GUILayout.Button("Add")) { Save(); } } else { if (GUILayout.Button("Save")) { Save(); } } EditorGUI.EndDisabledGroup(); EditorGUI.EndDisabledGroup(); if (GUILayout.Button("Cancel")) { Close(); GUIUtility.ExitGUI(); } EditorGUILayout.EndHorizontal(); } } private void Save() { if (registry.isValidCredential() && !string.IsNullOrEmpty(registry.token)) { credentialManager.SetCredential(registry.url, registry.auth, registry.token); credentialManager.Write(); // TODO figure out in which cases/Editor versions a restart is actually required, // and where a Client.Resolve() call or PackMan reload is sufficient if (EditorUtility.DisplayDialog("Unity Editor restart might be required", "The Unity editor might need to be restarted for this change to take effect.", "Restart Now", "Cancel")) { EditorApplication.OpenProject(Environment.CurrentDirectory); } Close(); GUIUtility.ExitGUI(); } else { EditorUtility.DisplayDialog("Invalid", "Invalid settings for credential.", "Ok"); } } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/UI/CredentialEditorView.cs.meta ================================================ fileFormatVersion: 2 guid: aed147722dfd212f18ab415b2ced4be3 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/UI/CredentialManagerView.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using Halodi.PackageRegistry.Core; using UnityEditor; using UnityEditorInternal; using UnityEngine; namespace Halodi.PackageRegistry.UI { public class CredentialManagerView : EditorWindow { private ReorderableList drawer; void OnEnable() { drawer = GetCredentialList(new CredentialManager()); minSize = new Vector2(640, 320); } void OnGUI() { drawer.DoLayoutList(); } internal static ReorderableList GetCredentialList(CredentialManager credentialManager) { ReorderableList credentialList = null; credentialList = new ReorderableList(credentialManager.CredentialSet, typeof(NPMCredential), false, true, true, true) { drawHeaderCallback = rect => { GUI.Label(rect, "User Credentials on this computer"); }, drawElementCallback = (rect, index, active, focused) => { var credential = credentialList.list[index] as NPMCredential; if (credential == null) return; rect.width -= 60; EditorGUI.LabelField(rect, credential.url); rect.x += rect.width; rect.width = 60; if (GUI.Button(rect, "Edit")) { CredentialEditorView credentialEditor = EditorWindow.GetWindow(true, "Edit credential", true); credentialEditor.Edit(credential, credentialManager); } }, onAddCallback = reorderableList => { CredentialEditorView credentialEditor = EditorWindow.GetWindow(true, "Add credential", true); credentialEditor.CreateNew(credentialManager); }, onRemoveCallback = reorderableList => { var credential = credentialList.list[credentialList.index] as NPMCredential; credentialManager.RemoveCredential(credential.url); credentialManager.Write(); } }; return credentialList; } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/UI/CredentialManagerView.cs.meta ================================================ fileFormatVersion: 2 guid: 6f1896f0b6cdab862b835b3498da53bd MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/UI/GetTokenView.cs ================================================ using System; using Halodi.PackageRegistry.Core; using Halodi.PackageRegistry.NPM; using UnityEditor; using UnityEngine; namespace Halodi.PackageRegistry.UI { internal class TokenMethod : GUIContent { internal delegate bool GetToken(ScopedRegistry registry, string username, string password); internal string usernameName; internal string passwordName; internal GetToken action; public TokenMethod(string name, string usernameName, string passwordName, GetToken action) : base(name) { this.usernameName = usernameName; this.passwordName = passwordName; this.action = action; } } internal class GetTokenView : EditorWindow { private static TokenMethod[] methods = { new TokenMethod("npm login", "Registry username", "Registry password", GetNPMLoginToken), new TokenMethod("bintray", "Bintray username", "Bintray API key", GetBintrayToken), // TODO adjust TokenMethod to allow for opening GitHub token URL: https://github.com/settings/tokens/new }; private string username; private string password; private bool initialized = false; private TokenMethod tokenMethod; private ScopedRegistry registry; void OnEnable() { error = null; } void OnDisable() { initialized = false; } private void SetRegistry(TokenMethod tokenMethod, ScopedRegistry registry) { this.tokenMethod = tokenMethod; this.registry = registry; this.initialized = true; } void OnGUI() { if (initialized) { EditorGUILayout.LabelField(tokenMethod, EditorStyles.whiteLargeLabel); username = EditorGUILayout.TextField(tokenMethod.usernameName, username); password = EditorGUILayout.PasswordField(tokenMethod.passwordName, password); if (GUILayout.Button("Login")) { if(tokenMethod.action(registry, username, password)) { CloseWindow(); } } if (GUILayout.Button("Close")) { CloseWindow(); } if (!string.IsNullOrEmpty(error)) { EditorGUILayout.HelpBox(error, MessageType.Error); } } } private static void CreateWindow(TokenMethod method, ScopedRegistry registry) { GetTokenView getTokenView = EditorWindow.GetWindow(true, "Get token", true); getTokenView.SetRegistry(method, registry); } private static string error = null; private static bool GetNPMLoginToken(ScopedRegistry registry, string username, string password) { NPMResponse response = NPMLogin.GetLoginToken(registry.url, username, password); if (string.IsNullOrEmpty(response.ok)) { // EditorUtility.DisplayDialog("Cannot get token", response.error, "Ok"); error = "Cannot get token: " + response.error; return false; } else { registry.token = response.token; return true; } } private static bool GetBintrayToken(ScopedRegistry registry, string username, string password) { registry.token = NPMLogin.GetBintrayToken(username, password); return !string.IsNullOrEmpty(registry.token); } private void CloseWindow() { error = null; foreach (var view in Resources.FindObjectsOfTypeAll()) { view.Repaint(); } Close(); GUIUtility.ExitGUI(); } internal static int CreateGUI(int selectedIndex, ScopedRegistry registry) { EditorGUILayout.LabelField("Generate token", EditorStyles.whiteLargeLabel); EditorGUILayout.BeginHorizontal(); selectedIndex = EditorGUILayout.Popup(new GUIContent("Method"), selectedIndex, methods); if(GUILayout.Button("Login & get auth token")) { CreateWindow(methods[selectedIndex], registry); } EditorGUILayout.EndHorizontal(); return selectedIndex; } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/UI/GetTokenView.cs.meta ================================================ fileFormatVersion: 2 guid: c114ccf58900f5e5ab2f67306c0f40c2 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/UI/InstallPackageCreator.cs ================================================ using System; using System.Collections.Generic; using Halodi.PackageRegistry.Core; using UnityEditor; using UnityEngine; namespace Halodi.PackageRegistry.UI { internal class InstallPackageCreatorView : EditorWindow { //[MenuItem("Packages/Install Halodi Package Creator", false, 41)] internal static void ManageRegistries() { UnityEditor.PackageManager.Client.Add("com.halodi.halodi-unity-package-creator"); } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/UI/InstallPackageCreator.cs.meta ================================================ fileFormatVersion: 2 guid: 7c16af11ea1aab931a6cc447c7b7bd7d MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/UI/RegistryManagerView.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using Halodi.PackageRegistry.Core; using UnityEditor; using UnityEditorInternal; using UnityEngine; namespace Halodi.PackageRegistry.UI { public class RegistryManagerView : EditorWindow { [MenuItem("ET/Init/Manage scoped registries", false, 21)] internal static void ManageRegistries() { SettingsService.OpenProjectSettings("Project/Package Manager/Credentials"); } private ReorderableList drawer; void OnEnable() { drawer = GetRegistryList(new RegistryManager()); minSize = new Vector2(640, 320); } void OnGUI() { EditorGUILayout.LabelField("Scoped registries", EditorStyles.whiteLargeLabel); drawer.DoLayoutList(); } internal static ReorderableList GetRegistryList(RegistryManager registryManager) { ReorderableList registryList = null; registryList = new ReorderableList(registryManager.registries, typeof(ScopedRegistry), false, true, true, true) { drawHeaderCallback = rect => { GUI.Label(rect, "Scoped Registries in this project"); }, elementHeight = 70, drawElementCallback = (rect, index, active, focused) => { var registry = registryList.list[index] as ScopedRegistry; if (registry == null) return; bool registryHasAuth = !string.IsNullOrEmpty(registry.token) && registry.isValidCredential(); var rect2 = rect; rect.width -= 60; rect.height = 20; GUI.Label(rect, registry.name + " (" + registry.scopes.Count + " scopes)", EditorStyles.boldLabel); rect.y += 20; GUI.Label(rect, registry.url); rect.y += 20; EditorGUI.BeginDisabledGroup(true); GUI.Toggle(rect, registryHasAuth, "Has Credentials"); EditorGUI.EndDisabledGroup(); rect2.x = rect2.xMax - 60; rect2.height = 20; rect2.width = 60; rect2.y += 5; if (GUI.Button(rect2, "Edit")) { ScopedRegistryEditorView registryEditor = EditorWindow.GetWindow(true, "Edit registry", true); registryEditor.Edit(registry, registryManager); } }, onAddCallback = list => { ScopedRegistryEditorView registryEditor = EditorWindow.GetWindow(true, "Add registry", true); registryEditor.CreateNew(registryManager); }, onRemoveCallback = list => { Debug.Log("index to remove: " + list.index); var entry = list.list[list.index] as ScopedRegistry; registryManager.Remove(entry); } }; return registryList; } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/UI/RegistryManagerView.cs.meta ================================================ fileFormatVersion: 2 guid: 0bb92217c830c75c4baccac1d1455d6a MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/UI/ScopedRegistryEditorView.cs ================================================ using System; using Halodi.PackageRegistry.Core; using UnityEditor; using UnityEditorInternal; using UnityEngine; namespace Halodi.PackageRegistry.UI { class ScopedRegistryEditorView : EditorWindow { private bool initialized = false; private RegistryManager controller; private bool createNew; private ScopedRegistry registry; private int tokenMethod; void OnEnable() { tokenMethod = 0; minSize = new Vector2(480, 320); } void OnDisable() { initialized = false; } public void CreateNew(RegistryManager controller) { this.controller = controller; this.createNew = true; this.registry = new ScopedRegistry(); this.initialized = true; } public void Edit(ScopedRegistry registry, RegistryManager controller) { this.controller = controller; this.registry = registry; this.createNew = false; this.initialized = true; } private ReorderableList scopeList = null; void OnGUI() { if (initialized) { EditorGUILayout.Space(); if (createNew) { EditorGUILayout.LabelField("Add scoped registry ", EditorStyles.whiteLargeLabel); registry.name = EditorGUILayout.TextField("Name", registry.name); EditorGUI.BeginChangeCheck(); registry.url = EditorGUILayout.TextField("URL", registry.url); if (EditorGUI.EndChangeCheck()) { UpdateCredential(); } } else { EditorGUILayout.LabelField("Edit scoped registry", EditorStyles.whiteLargeLabel); EditorGUILayout.LabelField("Name", registry.name); EditorGUILayout.LabelField("URL", registry.url); } if (scopeList == null) { scopeList = new ReorderableList(registry.scopes, typeof(string), true, false, true, true) { drawElementCallback = (rect, index, active, focused) => { registry.scopes[index] = EditorGUI.TextField(rect, registry.scopes[index]); }, onAddCallback = list => { registry.scopes.Add(""); } }; } EditorGUILayout.BeginHorizontal(); EditorGUILayout.PrefixLabel("Package Scopes"); EditorGUILayout.BeginVertical(); scopeList.DoLayoutList(); EditorGUILayout.EndVertical(); EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); EditorGUILayout.LabelField("Authentication / Credentials", EditorStyles.whiteLargeLabel); registry.auth = EditorGUILayout.Toggle("Always auth", registry.auth); registry.token = EditorGUILayout.TextField("Token", registry.token); EditorGUILayout.Space(); tokenMethod = GetTokenView.CreateGUI(tokenMethod, registry); EditorGUILayout.Space(); EditorGUILayout.BeginVertical(GUILayout.ExpandHeight(true)); EditorGUILayout.EndVertical(); EditorGUILayout.HelpBox("Restart Unity to reload credentials after saving.", MessageType.Info); EditorGUILayout.BeginHorizontal(); if (createNew) { if (GUILayout.Button("Add")) { Add(); } } else { if (GUILayout.Button("Save")) { Save(); } } if (GUILayout.Button("Cancel")) { Close(); GUIUtility.ExitGUI(); } EditorGUILayout.EndHorizontal(); } } private void Save() { if (registry.isValid()) { controller.Save(registry); Close(); GUIUtility.ExitGUI(); } else { EditorUtility.DisplayDialog("Invalid", "Invalid settings for registry.", "Ok"); } } private void Add() { if (registry.isValid()) { controller.Save(registry); Close(); GUIUtility.ExitGUI(); } else { EditorUtility.DisplayDialog("Invalid", "Invalid settings for registry.", "Ok"); } } private void UpdateCredential() { if (controller.credentialManager.HasRegistry(registry.url)) { NPMCredential cred = controller.credentialManager.GetCredential(registry.url); registry.auth = cred.alwaysAuth; registry.token = cred.token; } } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/UI/ScopedRegistryEditorView.cs.meta ================================================ fileFormatVersion: 2 guid: 0fa2f00a8426714b1a94a89df408ce1c MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/UI/SettingsProvider.cs ================================================ using System; using System.Collections.Generic; using Halodi.PackageRegistry.Core; using UnityEditor; using UnityEditor.PackageManager; using UnityEditor.SceneManagement; using UnityEditorInternal; using UnityEngine; namespace Halodi.PackageRegistry.UI { static class CredentialManagerSettingsProvider { [SettingsProvider] public static SettingsProvider CreateMyCustomSettingsProvider() { RegistryManager registryManager; ReorderableList credentialDrawer = null; ReorderableList registryDrawer = null; var provider = new SettingsProvider("Project/Package Manager/Credentials", SettingsScope.Project) { label = "Credentials", activateHandler = (str, v) => { registryManager = new RegistryManager(); registryDrawer = RegistryManagerView.GetRegistryList(registryManager); credentialDrawer = CredentialManagerView.GetCredentialList(registryManager.credentialManager); }, guiHandler = (searchContext) => { ThirdPartyInfo(); EditorGUILayout.Space(); registryDrawer.DoLayoutList(); EditorGUILayout.Space(); credentialDrawer.DoLayoutList(); }, footerBarGuiHandler = () => { EditorGUILayout.BeginHorizontal(); #if UNITY_2020_1_OR_NEWER if (GUILayout.Button("Reload Packages")) { // call internal PackageManagerWindow.ResetPackageDatabase(); var packageManagerWindow = typeof(Client).Assembly.GetType("UnityEditor.PackageManager.UI.PackageManagerWindow"); if (packageManagerWindow != null) packageManagerWindow.GetMethod("ResetPackageDatabase")?.Invoke(packageManagerWindow, new object[] { }); Client.Resolve(); } #endif if (GUILayout.Button("Restart Editor")) { if(EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) EditorApplication.OpenProject(Environment.CurrentDirectory); } EditorGUILayout.EndHorizontal(); }, // Populate the search keywords to enable smart search filtering and label highlighting keywords = new HashSet(new[] { "UPM", "NPM", "Credentials", "Packages", "Authentication", "Scoped Registry" }) }; return provider; } private static class Styles { public static readonly GUIStyle linkLabel; static Styles() { #if UNITY_2019_2_OR_NEWER linkLabel = new GUIStyle(EditorStyles.linkLabel); #else linkLabel = new GUIStyle(EditorStyles.miniLabel); #endif linkLabel.fontSize = EditorStyles.miniLabel.fontSize; linkLabel.wordWrap = true; } } private static void ThirdPartyInfo() { EditorGUILayout.HelpBox("Packages on scoped registries are provided by third parties.\n", MessageType.Info); var lastRect = GUILayoutUtility.GetLastRect(); lastRect.xMin = lastRect.xMax - 60; lastRect.yMin = lastRect.yMax - 20; if (GUI.Button(lastRect, "Read more", Styles.linkLabel)) Application.OpenURL("https://docs.unity3d.com/Documentation/Manual/upm-scoped.html"); EditorGUIUtility.AddCursorRect(lastRect, MouseCursor.Link); } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/UI/SettingsProvider.cs.meta ================================================ fileFormatVersion: 2 guid: ef531c9865ba9df44a7b77c821d7bf81 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/UI/UpgradePackagesView.cs ================================================ using System; using System.Collections.Generic; using Halodi.PackageRegistry.Core; using UnityEditor; using UnityEngine; using static Halodi.PackageRegistry.Core.UpgradePackagesManager; namespace Halodi.PackageRegistry.UI { internal class UpgradePackagesView : EditorWindow { //[MenuItem("Packages/Upgrade Packages", false, 23)] internal static void ManageRegistries() { EditorWindow.GetWindow(true, "Upgrade packages", true); } private UpgradePackagesManager manager; private bool upgradeAll; private bool showPreviewPackages = false; private bool useVerified = true; private Dictionary upgradeList = new Dictionary(); void OnEnable() { manager = new UpgradePackagesManager(); minSize = new Vector2(640, 320); upgradeAll = false; } void OnDisable() { manager = null; } private Vector2 scrollPos; private void Package(PackageUpgradeState info) { if(info.HasNewVersion(showPreviewPackages, useVerified)) { GUIStyle boxStyle = new GUIStyle(); boxStyle.padding = new RectOffset(10, 10, 0, 0); EditorGUILayout.BeginHorizontal(boxStyle); EditorGUI.BeginChangeCheck(); bool upgrade = false; if (upgradeList.ContainsKey(info)) { upgrade = upgradeList[info]; } upgrade = EditorGUILayout.BeginToggleGroup(info.GetCurrentVersion(), upgrade); if (EditorGUI.EndChangeCheck()) { if (!upgrade) { upgradeAll = false; } } upgradeList[info] = upgrade; EditorGUILayout.EndToggleGroup(); EditorGUILayout.LabelField(info.GetNewestVersion(showPreviewPackages, useVerified)); EditorGUILayout.EndHorizontal(); } } void OnGUI() { if (manager != null) { manager.Update(); EditorGUILayout.LabelField("Upgrade packages", EditorStyles.whiteLargeLabel); showPreviewPackages = EditorGUILayout.ToggleLeft("Show Preview Packages", showPreviewPackages); useVerified = EditorGUILayout.ToggleLeft("Prefer verified packages", useVerified); if (manager.packagesLoaded) { scrollPos = EditorGUILayout.BeginScrollView(scrollPos); foreach (var info in manager.UpgradeablePackages) { Package(info); } EditorGUILayout.EndScrollView(); EditorGUI.BeginChangeCheck(); upgradeAll = EditorGUILayout.ToggleLeft("Upgrade all packages", upgradeAll); if (EditorGUI.EndChangeCheck()) { foreach (var info in manager.UpgradeablePackages) { upgradeList[info] = upgradeAll; } } EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("Upgrade")) { Upgrade(); CloseWindow(); } if (GUILayout.Button("Close")) { CloseWindow(); } EditorGUILayout.EndHorizontal(); } else { EditorGUILayout.LabelField("Loading packages...", EditorStyles.whiteLargeLabel); } } } private void Upgrade() { if (manager != null) { EditorUtility.DisplayProgressBar("Upgrading packages", "Starting", 0); string output = ""; bool failures = false; try { foreach (var info in manager.UpgradeablePackages) { if(upgradeList.ContainsKey(info)) { if(upgradeList[info]) { EditorUtility.DisplayProgressBar("Upgrading packages", "Upgrading " + info.info.displayName, 0.5f); string error = ""; if (manager.UpgradePackage(info.GetNewestVersion(showPreviewPackages, useVerified), ref error)) { output += "[Success] Upgraded " + info.info.displayName + Environment.NewLine; } else { output += "[Error] Failed upgrade of" + info.info.displayName + " with error: " + error + Environment.NewLine; failures = true; } } } } } finally { EditorUtility.ClearProgressBar(); } string message; if (failures) { message = "Upgraded with errors." + Environment.NewLine + output; } else { message = "Upgraded all packages. " + Environment.NewLine + output; } EditorUtility.DisplayDialog("Upgrade finished", message, "OK"); } } private void CloseWindow() { Close(); GUIUtility.ExitGUI(); } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/UI/UpgradePackagesView.cs.meta ================================================ fileFormatVersion: 2 guid: 1f38c8e7a61df01009d0a1d9a8cd169e MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry/UI.meta ================================================ fileFormatVersion: 2 guid: a67360ebdc1e6d247b183859bd4be8a1 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi/PackageRegistry.meta ================================================ fileFormatVersion: 2 guid: e0dc91072aad56afa8bf4204ca72e442 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi.PackageRegistryManager.Editor.asmdef ================================================ { "name": "Halodi.PackageRegistryManager.Editor", "rootNamespace": "", "references": [ "Artees.SemanticVersioning" ], "includePlatforms": [ "Editor" ], "excludePlatforms": [], "allowUnsafeCode": false, "overrideReferences": true, "precompiledReferences": [ "Newtonsoft.Json.dll", "Unity.SharpZipLib.dll", "Tomlyn.dll" ], "autoReferenced": false, "defineConstraints": [], "versionDefines": [], "noEngineReferences": false } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi.PackageRegistryManager.Editor.asmdef.meta ================================================ fileFormatVersion: 2 guid: 69fe18a9ea685ee439ed87927dfee5c7 AssemblyDefinitionImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/Halodi.meta ================================================ fileFormatVersion: 2 guid: 089bef75d43e1ee5888d3be1e71e7ac4 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/LICENSE.Tomlyn.md ================================================ Copyright (c) 2019, Alexandre Mutel All rights reserved. Redistribution and use in source and binary forms, with or without modification , are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/LICENSE.Tomlyn.md.meta ================================================ fileFormatVersion: 2 guid: 475ab608e6dbf99409204c1b770d6562 TextScriptImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/README.md ================================================ # Third party libraries To avoid conflicts between packages all third party managed DLL's are prefixed with "860BB79D", which is the CRC32 hash of "com.halodi.halodi-unity-package-creator". Due to the 260 character limit on windows paths, we avoid prefixing the DLL names with "com.halodi.halodi-unity-package-reigstry-manager" ## NewtonSoft JSON https://www.newtonsoft.com/json License: MIT (see LICENSE.Newtonsoft.Json) ## SharpZipLib https://github.com/icsharpcode/SharpZipLib License: MIT ## Tomlyn https://github.com/xoofx/Tomlyn License: BSD-2-Clause (see LICENSE.Tomlyn) ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/README.md.meta ================================================ fileFormatVersion: 2 guid: 0006809df3aab01ef9e2bb535d02ebd9 TextScriptImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Tomlyn.dll.meta ================================================ fileFormatVersion: 2 guid: fe280739c6633ac4682879c184af804f PluginImporter: externalObjects: {} serializedVersion: 2 iconMap: {} executionOrder: {} defineConstraints: [] isPreloaded: 0 isOverridable: 1 isExplicitlyReferenced: 0 validateReferences: 1 platformData: - first: Any: second: enabled: 0 settings: {} - first: Editor: Editor second: enabled: 1 settings: DefaultValueInitialized: true - first: Windows Store Apps: WindowsStoreApps second: enabled: 0 settings: CPU: AnyCPU userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/Artees.UnitySemVer.asmdef ================================================ { "name": "Artees.SemanticVersioning", "references": [], "includePlatforms": [], "excludePlatforms": [], "allowUnsafeCode": false, "overrideReferences": true, "precompiledReferences": [], "autoReferenced": true, "defineConstraints": [], "versionDefines": [], "noEngineReferences": false } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/Artees.UnitySemVer.asmdef.meta ================================================ fileFormatVersion: 2 guid: 807287499d1ce5fd6a509eb37593f7da AssemblyDefinitionImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/CloudBuildManifest.cs ================================================ using System; using UnityEngine; namespace Artees.UnitySemVer { /// /// A parsed Unity Cloud Build manifest. /// internal class CloudBuildManifest { private static CloudBuildManifest _instance; public static CloudBuildManifest Instance => _instance ?? (_instance = new CloudBuildManifest()); /// /// Returns true if the manifest has been successfully loaded. /// public readonly bool IsLoaded; /// /// The Unity Cloud Build “build number” corresponding to this build. /// public readonly int BuildNumber; private CloudBuildManifest() { var manifestAsset = Resources.Load("UnityCloudBuildManifest.json"); if (manifestAsset == null) return; var manifest = manifestAsset.text; IsLoaded = true; const string key = "\"buildNumber\""; const StringComparison comparison = StringComparison.Ordinal; var keyStart = manifest.IndexOf(key, comparison); var valueStart = manifest.IndexOf("\"", keyStart + key.Length, comparison) + 1; var valueEnd = manifest.IndexOf("\"", valueStart, comparison); var buildNumber = manifest.Substring(valueStart, valueEnd - valueStart); int.TryParse(buildNumber, out BuildNumber); } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/CloudBuildManifest.cs.meta ================================================ fileFormatVersion: 2 guid: 3827c13e6b66fbeaa8fa6d86711b6ab2 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/LICENSE ================================================ MIT License Copyright (c) 2019 Artees Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/LICENSE.meta ================================================ fileFormatVersion: 2 guid: 3a5b2e83aa2757a0e8adc6834aff5a52 DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/README.md ================================================ # Unity SemVer [![openupm](https://img.shields.io/npm/v/games.artees.semver?label=openupm®istry_uri=https://package.openupm.com)](https://openupm.com/packages/games.artees.semver/) A convenient way to edit and compare version numbers according to the [Semantic Versioning 2.0.0](https://semver.org/) specification. Also includes a property drawer for Unity. ![Property drawer](https://github.com/Artees/Unity-SemVer/raw/master/SemVerDrawer.png) # Installation Install the package **games.artees.semver** using [my package registry](https://artees.games/upm). Or install via the [OpenUPM registry](https://openupm.com/packages/games.artees.semver/). # Usage Use the `Artees.UnitySemVer.SemVer` class or apply the `Artees.UnitySemVer.SemVerAttribute` attribute to a string field. ``` public SemVer version = new SemVer {major = 1, minor = 2, patch = 3}; [SemVer] public string versionString = "1.2.3"; ``` Parsing: ``` var version = SemVer.Parse("2.0.0-rc.1+build.123"); ``` Comparing: ``` Debug.Log("2.1.0" > version); ``` Validating: ``` var result = version.Validate(); version = result.Corrected; foreach (var message in result.Errors) { Debug.LogWarning(message); } ``` ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/README.md.meta ================================================ fileFormatVersion: 2 guid: 21d9cfaa2380b79739f0f756280752f3 TextScriptImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/SemVer.cs ================================================ using System; using UnityEngine; namespace Artees.UnitySemVer { /// /// A semantic version based on the Semantic Versioning 2.0.0 specification. /// [Serializable] public class SemVer : IComparable, IEquatable { public const char IdentifiersSeparator = '.'; public const char PreReleasePrefix = '-'; public const char BuildPrefix = '+'; public static bool operator ==(SemVer left, SemVer right) { return Equals(left, right); } public static bool operator !=(SemVer left, SemVer right) { return !Equals(left, right); } public static bool operator >(SemVer left, SemVer right) { return left.CompareTo(right) > 0; } public static bool operator <(SemVer left, SemVer right) { return left.CompareTo(right) < 0; } public static bool operator >=(SemVer left, SemVer right) { return left.CompareTo(right) >= 0; } public static bool operator <=(SemVer left, SemVer right) { return left.CompareTo(right) <= 0; } public static implicit operator string(SemVer s) { return s.ToString(); } public static implicit operator SemVer(string s) { return Parse(s); } public static SemVer Parse(string semVer) { return SemVerConverter.FromString(semVer); } /// /// Major version X (X.y.z | X > 0) MUST be incremented if any backwards incompatible changes are introduced /// to the public API. It MAY include minor and patch level changes. Patch and minor version MUST be reset to 0 /// when major version is incremented. /// /// public uint major; /// /// Minor version Y (x.Y.z | x > 0) MUST be incremented if new, backwards compatible functionality is /// introduced to the public API. It MUST be incremented if any public API functionality is marked as /// deprecated. It MAY be incremented if substantial new functionality or improvements are introduced within /// the private code. It MAY include patch level changes. Patch version MUST be reset to 0 when minor version /// is incremented. /// /// public uint minor; /// /// Patch version Z (x.y.Z | x > 0) MUST be incremented if only backwards compatible bug fixes are introduced. /// public uint patch; /// /// A pre-release version indicates that the version is unstable and might not satisfy the intended /// compatibility requirements as denoted by its associated normal version. /// /// 1.0.0-alpha, 1.0.0-alpha.1, 1.0.0-0.3.7, 1.0.0-x.7.z.92 public string preRelease; /// /// Set the build metadata automatically /// /// public SemVerAutoBuild.Type autoBuild; [SerializeField] private string build = string.Empty; /// /// Build metadata MUST be ignored when determining version precedence. Thus two versions that differ only in /// the build metadata, have the same precedence. /// /// 1.0.0-alpha+001, 1.0.0+20130313144700, 1.0.0-beta+exp.sha.5114f85 public string Build { get => SemVerAutoBuild.Instances[autoBuild].Get(build); set => build = SemVerAutoBuild.Instances[autoBuild].Set(value); } /// /// The base part of the version number (Major.Minor.Patch). /// /// 1.9.0 /// Major.Minor.Patch public string Core => $"{major}.{minor}.{patch}"; /// /// An internal version number. This number is used only to determine whether one version is more recent than /// another, with higher numbers indicating more recent versions. /// /// /// Major * 10000 + Minor * 100 + Patch public int AndroidBundleVersionCode { get { var clampedPatch = ClampAndroidBundleVersionCode(patch, "Patch"); var clampedMinor = ClampAndroidBundleVersionCode(minor, "Minor"); return (int) (major * 10000 + clampedMinor * 100 + clampedPatch); } } private static uint ClampAndroidBundleVersionCode(uint value, string name) { uint clamped; const uint max = 100; if (value >= max) { clamped = max - 1; Debug.LogWarning(name + " should be less than " + max); } else { clamped = value; } return clamped; } public SemVer() { minor = 1; preRelease = string.Empty; autoBuild = SemVerAutoBuild.Type.Manual; } /// /// Increment the major version, reset the patch and the minor version to 0. /// public void IncrementMajor() { major++; minor = patch = 0; } /// /// Increment the minor version, reset the patch version to 0. /// public void IncrementMinor() { minor++; patch = 0; } /// /// Increment the patch version. /// public void IncrementPatch() { patch++; } /// /// Check if this semantic version meets the Semantic Versioning 2.0.0 /// specification. /// /// The result of validation and automatically corrected version number. public SemVerValidationResult Validate() { return new SemVerValidator().Validate(this); } /// /// Creates a copy of this semantic version. /// public SemVer Clone() { return new SemVer { major = major, minor = minor, patch = patch, preRelease = preRelease, Build = Build, autoBuild = autoBuild, }; } public int CompareTo(SemVer other) { return new SemVerComparer().Compare(this, other); } public bool Equals(SemVer other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return CompareTo(other) == 0; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; return obj.GetType() == GetType() && Equals((SemVer) obj); } public override int GetHashCode() { throw new NotImplementedException(); } public override string ToString() { return SemVerConverter.ToString(this); } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/SemVer.cs.meta ================================================ fileFormatVersion: 2 guid: 61d25493bd7efc0be91154722ca4f557 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/SemVerAttribute.cs ================================================ using UnityEngine; namespace Artees.UnitySemVer { /// /// Indicates that a string is a semantic version. It looks like in the Unity's Inspector. /// public class SemVerAttribute : PropertyAttribute { } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/SemVerAttribute.cs.meta ================================================ fileFormatVersion: 2 guid: a865e2b2f8f01fe74a15283aac262bdb MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/SemVerAutoBuild.cs ================================================ using System.Collections.Generic; using UnityEngine; namespace Artees.UnitySemVer { /// /// Sets the build metadata automatically /// /// public abstract class SemVerAutoBuild { /// /// implementations /// public enum Type { /// /// Disables automatic build metadata /// Manual, /// /// Sets the build metadata to the /// Unity Cloud Build /// “build number” /// /// CloudBuildNumber } public static readonly IReadOnlyDictionary Instances = new Dictionary { {Type.Manual, new ManualBuild()}, {Type.CloudBuildNumber, new CloudBuildNumberBuild()} }; internal abstract string Get(string build); internal abstract string Set(string build); private class ManualBuild : SemVerAutoBuild { internal override string Get(string build) { return build; } internal override string Set(string build) { return build; } } private class CloudBuildNumberBuild : ReadOnly { internal override string Get(string build) { return CloudBuildManifest.Instance.IsLoaded ? CloudBuildManifest.Instance.BuildNumber.ToString() : string.Empty; } } public abstract class ReadOnly : SemVerAutoBuild { internal sealed override string Set(string build) { Debug.LogWarning("The build metadata is read-only"); return build; } } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/SemVerAutoBuild.cs.meta ================================================ fileFormatVersion: 2 guid: 7da255b3007599433a91930f1a658967 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/SemVerComparer.cs ================================================ using System; using System.Collections.Generic; using UnityEngine; namespace Artees.UnitySemVer { internal class SemVerComparer : IComparer { public int Compare(SemVer x, SemVer y) { if (ReferenceEquals(x, y)) return 0; if (ReferenceEquals(null, y)) return 1; if (ReferenceEquals(x, null)) return -1; var majorComparison = x.major.CompareTo(y.major); if (majorComparison != 0) return majorComparison; var minorComparison = x.minor.CompareTo(y.minor); if (minorComparison != 0) return minorComparison; var patchComparison = x.patch.CompareTo(y.patch); return patchComparison != 0 ? patchComparison : ComparePreReleaseVersions(x, y); } private static int ComparePreReleaseVersions(SemVer x, SemVer y) { if (IsPreRelease(x)) { if (!IsPreRelease(y)) return -1; } else { return IsPreRelease(y) ? 1 : 0; } var xIdentifiers = x.preRelease.Split(SemVer.IdentifiersSeparator); var yIdentifiers = y.preRelease.Split(SemVer.IdentifiersSeparator); var length = Mathf.Min(xIdentifiers.Length, yIdentifiers.Length); for (var i = 0; i < length; i++) { var xIdentifier = xIdentifiers[i]; var yIdentifier = yIdentifiers[i]; if (Equals(xIdentifier, yIdentifier)) continue; return ComparePreReleaseIdentifiers(xIdentifier, yIdentifier); } return xIdentifiers.Length.CompareTo(yIdentifiers.Length); } private static bool IsPreRelease(SemVer semVer) { return !string.IsNullOrEmpty(semVer.preRelease); } private static int ComparePreReleaseIdentifiers(string xIdentifier, string yIdentifier) { var isXNumber = int.TryParse(xIdentifier, out var xNumber); var isYNumber = int.TryParse(yIdentifier, out var yNumber); if (!isXNumber) { const StringComparison comparison = StringComparison.Ordinal; return isYNumber ? 1 : string.Compare(xIdentifier, yIdentifier, comparison); } if (isYNumber) { return xNumber.CompareTo(yNumber); } return -1; } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/SemVerComparer.cs.meta ================================================ fileFormatVersion: 2 guid: 2e4f2656aa63c5aae9f0e95a538481a2 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/SemVerConverter.cs ================================================ namespace Artees.UnitySemVer { internal static class SemVerConverter { public static SemVer FromString(string semVerString) { var strings = semVerString.Split(SemVer.IdentifiersSeparator, SemVer.PreReleasePrefix, SemVer.BuildPrefix); var preReleaseStart = semVerString.IndexOf(SemVer.PreReleasePrefix); var buildIndex = semVerString.IndexOf(SemVer.BuildPrefix); var preReleaseEnd = buildIndex >= 0 ? buildIndex : semVerString.Length; var preRelease = preReleaseStart >= 0 ? semVerString.Substring(preReleaseStart + 1, preReleaseEnd - preReleaseStart - 1) : string.Empty; var build = buildIndex >= 0 ? semVerString.Substring(buildIndex + 1) : string.Empty; uint major = 0; if (strings.Length > 0) uint.TryParse(strings[0], out major); uint minor = 1; if (strings.Length > 1) uint.TryParse(strings[1], out minor); uint patch = 0; if (strings.Length > 2) uint.TryParse(strings[2], out patch); var semVer = new SemVer { major = major, minor = minor, patch = patch, preRelease = preRelease, Build = build }; return semVer; } public static string ToString(SemVer semVer) { var preRelease = string.IsNullOrEmpty(semVer.preRelease) ? string.Empty : $"{SemVer.PreReleasePrefix}{semVer.preRelease}"; var build = string.IsNullOrEmpty(semVer.Build) ? string.Empty : $"{SemVer.BuildPrefix}{semVer.Build}"; return string.Format("{1}{0}{2}{0}{3}{4}{5}", SemVer.IdentifiersSeparator, semVer.major, semVer.minor, semVer.patch, preRelease, build); } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/SemVerConverter.cs.meta ================================================ fileFormatVersion: 2 guid: 0f9e174e68f0a02129374b3ac91fe3d8 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/SemVerErrorMessage.cs ================================================ namespace Artees.UnitySemVer { public static class SemVerErrorMessage { public const string Empty = "Pre-release and build identifiers must not be empty"; public const string Invalid = "Pre-release and build identifiers must comprise only ASCII alphanumerics and hyphen"; public const string LeadingZero = "Numeric pre-release identifiers must not include leading zeroes"; } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/SemVerErrorMessage.cs.meta ================================================ fileFormatVersion: 2 guid: 2a7bb66e6d2ef54aca93374afae8bbed MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/SemVerValidationResult.cs ================================================ using System.Collections.ObjectModel; namespace Artees.UnitySemVer { /// /// Information returned about a checked semantic version. /// /// public class SemVerValidationResult { /// /// Error messages. This collection is empty if the version is valid. /// public readonly ReadOnlyCollection Errors; /// /// Automatically corrected semantic version. /// public readonly SemVer Corrected; /// /// Does the version meet the Semantic Versioning 2.0.0 specification? /// public bool IsValid => Errors.Count == 0; internal SemVerValidationResult(ReadOnlyCollection errors, SemVer corrected) { Errors = errors; Corrected = corrected; } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/SemVerValidationResult.cs.meta ================================================ fileFormatVersion: 2 guid: 52627243f647c2e3bbda3a99bc93f30c MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/SemVerValidator.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; namespace Artees.UnitySemVer { internal class SemVerValidator { private List _errors; private SemVer _corrected; public SemVerValidationResult Validate(SemVer semVer) { _errors = new List(); _corrected = semVer.Clone(); ValidatePreRelease(semVer); ValidateBuild(semVer); return new SemVerValidationResult(_errors.AsReadOnly(), _corrected.Clone()); } private void ValidatePreRelease(SemVer semVer) { var identifiers = ValidateIdentifiers(semVer.preRelease); identifiers = ValidateLeadingZeroes(identifiers); var joined = JoinIdentifiers(identifiers); _corrected.preRelease = joined; } private void ValidateBuild(SemVer semVer) { if (SemVerAutoBuild.Instances[semVer.autoBuild] is SemVerAutoBuild.ReadOnly) return; var identifiers = ValidateIdentifiers(semVer.Build); var joined = JoinIdentifiers(identifiers); _corrected.Build = joined; } private string[] ValidateIdentifiers(string identifiers) { if (string.IsNullOrEmpty(identifiers)) return new string[0]; var separators = new[] {SemVer.IdentifiersSeparator}; var strings = identifiers.Split(separators, StringSplitOptions.RemoveEmptyEntries); if (strings.Length < identifiers.Count(c => c == SemVer.IdentifiersSeparator) + 1) { _errors.Add(SemVerErrorMessage.Empty); } for (var i = 0; i < strings.Length; i++) { var raw = strings[i]; var corrected = Regex.Replace(raw, "[^0-9A-Za-z-]", "-"); if (string.Equals(raw, corrected)) continue; _errors.Add(SemVerErrorMessage.Invalid); strings[i] = corrected; } return strings; } private string[] ValidateLeadingZeroes(IList identifiers) { var length = identifiers.Count; var corrected = new string[length]; for (var i = 0; i < length; i++) { var identifier = identifiers[i]; var isNumeric = int.TryParse(identifier, out var numericIdentifier); if (isNumeric && identifier.StartsWith("0") && identifier.Length > 1) { corrected[i] = numericIdentifier.ToString(); _errors.Add(SemVerErrorMessage.LeadingZero); } else { corrected[i] = identifier; } } return corrected; } private static string JoinIdentifiers(string[] identifiers) { return string.Join(SemVer.IdentifiersSeparator.ToString(), identifiers); } } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer/SemVerValidator.cs.meta ================================================ fileFormatVersion: 2 guid: 7e9342dc023a6f090be08b41d4c907d2 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty/Unity-SemVer.meta ================================================ fileFormatVersion: 2 guid: 701bc2a7cddabf3a4885c944fc034394 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor/ThirdParty.meta ================================================ fileFormatVersion: 2 guid: ab3ad5cc6e9818cd498b049fe12c0c47 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Editor.meta ================================================ fileFormatVersion: 2 guid: 3f77bd7bac843b45d851f83be3888f20 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/LICENSE.md ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/LICENSE.md.meta ================================================ fileFormatVersion: 2 guid: 5924639ebe9748b8e86a3e462ddd84fe TextScriptImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/README.md ================================================ # Halodi Unity Package Registry Manager This package provides a simple UI to edit scoped registries and set credentials. To manage package creation and publication, take a look at [Halodi Unity Package Creator ](https://github.com/Halodi/halodi-unity-package-creator). ![Screenshot of main functionality](Documentation~/halodi-unity-package-registry-manager-screenshot.png) ## License Apache 2.0 ## Installation In Unity, go to Window -> Package Manager. Add package from git url (press the + in the top left of the Window). Use the repository URL for this package: ``` https://github.com/Halodi/halodi-unity-package-registry-manager.git ``` ## Usage After installation a new menu "Packages" will appear. ### Manage credentials Under "Manage credential" you can add, edit and remove credentials in ~/.upmconfig.toml. Each registry logs in using a token. If your NPM provider provides a token directly, enter it here. If your provider requires a login, select the method and press "Get Token". Enter required information and press "Login". A token will be requested from the registry. The login information will not get saved. To always authenticate, set "Always auth" to true; After setting the registry credentials, it is advised to restart Unity to reload the package manager. #### Notes for specific providers **Github**: Create a [Personal access token](https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line), make sure to select "read:packages" for adding packages to a project and "write:packages" if you want to publish packages. Copy the personal access token directly in the "Token" field (ignore Generate Token). **Bintray**: In Generate Token, select "bintray" as method and press "Get token". Enter your credentials. Note: Your credentials are not checked here, the token is calculated from your credentials. **Verdaccio**: In Generate Token, select "npm login" as method and press "Get token". Enter your credentials. ### Manage scoped registries Using "Manage scoped registries" you can add, edit and remove scoped registries from the projects package manifest.json. In the same dialog, you can set the credentials for scoped registries. If no token is set, the credentials will be removed from the credential database. For more information, see "Manage credentials". Note: The authentication settings are saved per-computer, not per project. If you have multiple projects with the same scoped registry, the authentication parameters will be shared. ### Add Packages (Bulk) Utility to add a list of packages. Utility could include - Sharing a list of packages to be bulk imported a project by copy-pasting - Adding packages when the registry does not support listing all packages Note: The Editor will freeze while downloading the packages. This is expected. A popup with the imported packages will appear when done. ## Library functions This library provides basic NPM functionality in [Halodi.PackageRegistry.NPM](Editor/Halodi/PackageRegistry/NPM.cs). The following functions are implemented ### NPM.GetLoginToken(string url, string user, string password) Same as "npm login --registry [url]". Returns a NPMResponse struct. If succesfull, NPMResponse.ok will be set and NPMResponse.token will contain the token. If a fault happend, NPMREsponse.error will be set to the error. ### NPM.Publish(string packageFolder, string registry) Same as "npm publish --registry [registry]" executed in "packageFolder". The publish functionality uses the credentials set in "~/.upmconfig.toml" to publish to the registry. Use "Manage registries" to set the credentials. The package tarball is created using SharpZipLib, to avoid compatibility issues with PackageManager.Client.Pack() in Unity. Throws a System.IO.IOException if an error occurs. ## Bugs and feature requests Please open a ticket on Github. Pull requests are encouraged. ## Maintainer [Jesper](jesper@halodi.com) ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/README.md.meta ================================================ fileFormatVersion: 2 guid: 851de30f36983cf75b87f89edfac329a TextScriptImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Tests/Editor/AssemblyInfo.cs ================================================ using System.Reflection; [assembly: AssemblyTitle("com.halodi.halodi-unity-package-registry-manager.Editor.Tests")] ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Tests/Editor/AssemblyInfo.cs.meta ================================================ fileFormatVersion: 2 guid: 6e0e426895d792d2ca7bc29a9b7e67f2 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Tests/Editor/Halodi.PackageRegistryManager.Tests.asmdef ================================================ { "name": "Halodi.PackageRegistryManager.Tests", "references": [ "UnityEngine.TestRunner", "UnityEditor.TestRunner", "Halodi.PackageRegistryManager.Editor" ], "includePlatforms": [ "Editor" ], "excludePlatforms": [], "allowUnsafeCode": false, "overrideReferences": true, "precompiledReferences": [ "nunit.framework.dll" ], "autoReferenced": false, "defineConstraints": [ "UNITY_INCLUDE_TESTS" ], "versionDefines": [], "noEngineReferences": false } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Tests/Editor/Halodi.PackageRegistryManager.Tests.asmdef.meta ================================================ fileFormatVersion: 2 guid: f56fe7affb0989254a398cff4738a865 AssemblyDefinitionImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Tests/Editor.meta ================================================ fileFormatVersion: 2 guid: d26e9d0d897f2ff3cb41c5d4d4c8985e folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/Tests.meta ================================================ fileFormatVersion: 2 guid: cb4cfc2933fda19ed95027bf0df94ac1 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/package.json ================================================ { "name": "com.halodi.halodi-unity-package-registry-manager", "version": "1.0.5", "displayName": "Halodi Package Registry Manager", "type": "tool", "samples": [], "description": "Simple tool to manage scoped registries and NPM credentials.", "license": "Apache-2.0", "author": { "name": "Halodi", "email": "jesper@halodi.com", "url": "https://halodi.com" }, "dependencies": { "com.unity.nuget.newtonsoft-json": "3.0.2", "com.unity.sharp-zip-lib": "1.3.4-preview" }, "publishConfig": { "registry": "" }, "repository": { "type": "git", "url": "https://github.com/Halodi/halodi-unity-package-registry-manager.git" } } ================================================ FILE: Packages/com.halodi.halodi-unity-package-registry-manager/package.json.meta ================================================ fileFormatVersion: 2 guid: 4480134b80472dc1a847e756ab3917cc PackageManifestImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/.editorconfig ================================================ root = true [*] indent_style = space indent_size = 2 end_of_line = lf ================================================ FILE: Packages/com.unity.ide.rider/.signature ================================================ { "timestamp": 1718627208, "signature": "XO721MNQKyDN0H6D282GzXcEQDBd9wgGlkoocQk3iz22Tjp0E9zHrXLlGRpHJv5ltn5k0Dmegdpvni4qdG7DWM1YIL8bp8fAq/r9Lh1gH8woZ1Q/d5O82XS30pNfpVzXus6EVDhxW0EpsE6Hzpmy6GSHGWYlYlfdP4fpLaK5Yoc3j/DKsgDGJUzkl3rkI96Ec0js4TgaA1b0hjftxOQN7bt+HloX8ltsbEp+t6/mqW6MzWqT1ewM8Rxz/UNRy9qncywyONwaSEqNvChcKWT4eJn6aGgt5Dg2MiDsYL9Zyz9lVc8Och3U860JE6y3XdA/0K5BKek2IMufJn8QQc7nrp3LaaS/CBwHEpuyrTSjLGrZzUz1JkcE4YqlRtv2D/VJhvLRm34PxdqcwNyIzH1b08R5U2zL/5TX1eajVx5mcnUizyW89dvzvhbwJQug3J7Eg9yjKCHtvK94IQ4GKd8coMwMWbgi2AT8aRZFCz/804hYxsVRyz3tozUGlUwcYD/X", "publicKey": "LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQm9qQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FZOEFNSUlCaWdLQ0FZRUFzdUhXYUhsZ0I1cVF4ZEJjTlJKSAordHR4SmoxcVY1NTdvMlZaRE1XaXhYRVBkRTBEMVFkT1JIRXNSS1RscmplUXlERU83ZlNQS0ZwZ1A3MU5TTnJCCkFHM2NFSU45aHNQVDhOVmllZmdWem5QTkVMenFkVmdEbFhpb2VpUnV6OERKWFgvblpmU1JWKytwbk9ySTRibG4KS0twelJlNW14OTc1SjhxZ1FvRktKT0NNRlpHdkJMR2MxSzZZaEIzOHJFODZCZzgzbUovWjBEYkVmQjBxZm13cgo2ZDVFUXFsd0E5Y3JZT1YyV1VpWXprSnBLNmJZNzRZNmM1TmpBcEFKeGNiaTFOaDlRVEhUcU44N0ZtMDF0R1ZwCjVNd1pXSWZuYVRUemEvTGZLelR5U0pka0tldEZMVGdkYXpMYlpzUEE2aHBSK0FJRTJhc0tLTi84UUk1N3UzU2cKL2xyMnZKS1IvU2l5eEN1Q20vQWJkYnJMbXk0WjlSdm1jMGdpclA4T0lLQWxBRWZ2TzV5Z2hSKy8vd1RpTFlzUQp1SllDM0V2UE16ZGdKUzdGR2FscnFLZzlPTCsxVzROY05yNWdveVdSUUJ0cktKaWlTZEJVWmVxb0RvSUY5NHpCCndGbzJJT1JFdXFqcU51M3diMWZIM3p1dGdtalFra3IxVjJhd3hmcExLWlROQWdNQkFBRT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg" } ================================================ FILE: Packages/com.unity.ide.rider/CHANGELOG.md ================================================ # Code Editor Package for Rider ## [3.0.31] - 2024-06-17 fix RIDER-104519 Rider is reporting errors in scripts that work fine in Unity when utilizing DOTS - when Player project, by generating projects for all assemblies in "com.unity.entities", "com.unity.collections" fix RIDER-111622 Unity Rider package is not compatible with Rider Dev builds ## [3.0.28] - 2024-02-20 - fix RIDER-103933 "PlayerSettings.suppressCommonWarnings" is not supported in Unity 2019.4.40f - fix https://github.com/JetBrains/resharper-unity/issues/2431 and [RIDER-104221](https://youtrack.jetbrains.com/issue/RIDER-104221) ## [3.0.27] - 2023-11-30 - Restore the ability to select Rider installation from the custom location - Fix possible extra project regeneration on moving focus from Rider to Unity - Improve performance of code generation for very large projects ## [3.0.26] - 2023-10-04 - https://github.com/JetBrains/resharper-unity/issues/2421 - https://github.com/JetBrains/resharper-unity/issues/2422 ## [3.0.25] - 2023-08-18 - unification of functionality to search JetBrains installations and open solution and file in Rider ## [3.0.22] - 2023-05-2 - RIDER-82999 Unity's plugin SyncAll does not regenerate project files, and instead does basically nothing. - #2401 Compilation issue with Unity 2021.3.0f1 ## [3.0.21] - 2023-04-18 [RIDER-92424](https://youtrack.jetbrains.com/issue/RIDER-92424) JetBrains Rider Editor 3.0.20 package Update for Unity, Cause's Rider to Slows to a Crawl after updating [RIDER-92419](https://youtrack.jetbrains.com/issue/RIDER-92419) JetBrains Rider Editor 3.0.20 for Unity has duplicate assemblies loaded into runtime ## [3.0.20] - 2023-04-05 - fix loading Rider integration EditorPlugin on first switch of External Editor to Rider, see [RIDER-91185](https://youtrack.jetbrains.com/issue/RIDER-91185) - Keep the the PackageManager in sync with the Rider changes made to the manifest.json, it should help with [RIDER-77343](https://youtrack.jetbrains.com/issue/RIDER-77343) - Support CompilerOptions.RoslynAdditionalFilePaths and CompilerOptions.AnalyzerConfigPath ## [3.0.18] - 2023-01-09 - [RIDER-74818](https://youtrack.jetbrains.com/issue/RIDER-74818) Unity doesn't get to play mode if Editor is not running and user starts debug or profiling - Improve performance of project generation - avoid using Directory.Exists - avoid doing ProjectGeneration twice on the first start-up ## [3.0.17] - 2022-12-01 - Avoid adding asset project parts to both editor and player projects, fixes the following issues: - [RIDER-75500](https://youtrack.jetbrains.com/issue/RIDER-75500) Local package references completions shows duplicate entries if player projects are generated - [RIDER-73795](https://youtrack.jetbrains.com/issue/RIDER-73795) Conversion to guid is not offered for assemblies with generated player projects - [RIDER-71238](https://youtrack.jetbrains.com/issue/RIDER-71238) No usages can be found for the assembly if player projects are generated ## [3.0.16] - 2022-09-09 - Update the changelog - Add folders to the generated csproj files - Avoid extra RequestScriptReload call on the first start - Fix shader support for folders in packages, but outside asmdef ## [3.0.15] - 2022-05-24 - Cleanup cache after project generation to reduce memory consumption - Performance optimization - RIDER-76126 Rider package should generate an empty csproj for empty Unity project - RIDER-77206 Unity 2020.1.3 'PlayerSettings' does not contain a definition for 'suppressCommonWarnings ## [3.0.14] - 2022-04-21 - Move Rider package persisted state to Library, to avoid vcs collisions or adding it specifically to gitignore ## [3.0.13] - 2022-03-24 - fix RIDER-69927 "Test not run" status is shown for the test suite when running unit tests for Unity project - fix RIDER-74676 Unity plugin "JetBrainseRider Editor" completely breaks <= 2019.1.9 - fix RIDER-71503 Unity Hang on "Domain Unload", caused by dispose of FileSystemWatcher ## [3.0.12] - 2022-01-28 - Fix bug, which was introduced in 3.0.10: New script was not added to the csproj, because cached list of assemblies was used. ## [3.0.10] - 2021-12-09 - Fix presentation of the TargetFramework in the csproj - Fix: Auto-generated solution doesn't compile when code overrides virtual functions in other assemblies - Fix RIDER-72234 Avoid full project generation, when only content of assembly was changed - Fix RIDER-71985 Building large Unity projects randomly fails - Fix RIDER-72174 Looking for Rider installed by dotUltimate installer ## [3.0.9] - 2021-11-09 - Fix path for Roslyn analyser supplied with a package - Minimal requirement for roslyn analyzer scope is Unity 2020.3.6f1 and above ## [3.0.8] - 2021-11-08 - Technical release ## [3.0.7] - 2021-05-07 - RIDER-60815 Simplify extensions lists for Rider package - Fix csc.rsp `-nullable+` / `-nullable-` parsing https://github.com/van800/com.unity.ide.rider/issues/7 - Support `-warnaserror`/`-warnaserror-:`/`-warnaserror+:` in csc.rsp ## [3.0.6] - 2021-04-06 - Fix bug: For Unity 2021.1+ Switching external editor from VS => Rider won't create the connection between Unity and Rider. - When PlayerSettings.suppressCommonWarnings is true, it is reflected in the generated csproj with NoWarn "0169", "0649" - By default include T4 templates in the generated solution (RIDER-37159) - RIDER-60554 Unity crash in case of project without Unity Test Framework Package. - RIDER-60445 Fix presentation of Rider external editor, when it is installed in a custom location. - Improve project files generation performance - RIDER-60508 Project Generation for projects without any cs files - add reference to UnityEditor/UnityEngine, so that Rider would detect Unity path and version and provide rich features for shader file. ## [3.0.5] - 2021-02-25 - More stable in case of possible Rider product code change, improve test. Allows using "Rider for Unreal" with Unity projects (https://youtrack.jetbrains.com/issue/RIDER-51203) - Remove implicit dependency to Test-Framework package - Fix "Unreachable code detected" warning (https://youtrack.jetbrains.com/issue/RIDER-57930) ## [3.0.4] - 2021-01-26 - Use LangVersion provided by Unity for generated csproj - Improve documentation - Support nullable provided in csc,rsp - Avoid doing work in Unity secondary processes in UNITY_2021_1_OR_NEWER with UnityEditor.MPE.ProcessLevel.Secondary ## [3.0.3] - 2020-11-18 - Update License - Avoid connecting Rider from secondary UnityEditor instances - Fix RIDER-53082 - Generate csproj without cs files, when there are any assets inside ## [3.0.2] - 2020-10-27 - Speedup ProjectGeneration - Fix RIDER-51958. Callbacks OnGeneratedCSProjectFiles would not work, but show a Warning instead. - Remove release configuration - Call RequestScriptReload, when External Editor is changed in Unity. ## [3.0.1] - 2020-10-02 - RIDER-46658 Rider does not run PlayMode tests when ValueSource is combined with parameterized TestFixture - RIDER-49947 Invoking `PlayerSettings.SetScriptingDefineSymbolsForGroup()` does not update definitions in Rider. - Add static entrypoint `Packages.Rider.Editor.RiderScriptEditor.SyncSolution` to allow generating solution from commandline. ## [2.0.7] - 2020-08-18 - Improve performance - Add support for asmdef Root Namespace in .csproj generation - ProjectGeneration for custom roslyn analysers https://docs.unity3d.com/2020.2/Documentation/Manual/roslyn-analyzers.html - Switch target platform in Unity would regenerate csproj files (https://github.com/JetBrains/resharper-unity/issues/1740) ## [2.0.6] - 2020-08-10 - Improve performance - Add support for asmdef Root Namespace in .csproj generation - ProjectGeneration for custom roslyn analysers https://docs.unity3d.com/2020.2/Documentation/Manual/roslyn-analyzers.html - Switch target platform in Unity would regenerate csproj files (https://github.com/JetBrains/resharper-unity/issues/1740) ## [2.0.5] - 2020-05-27 - Fix Regression in 2.0.3: In Unity 2019.2.9 on Mac, changing csproj and calling AssetDatabase.Refresh is not regenerating csproj. - Regenerate projects on changes in manifest.json and Project Settings (EditorOnlyScriptingUserSettings.json) (#51) - Fix: Assembly references to package assemblies break IDE projects. - Fix: Reporting test duration. ## [2.0.2] - 2020-03-18 - fix bug in searching Rider path on MacOS ## [2.0.1] - 2020-03-05 - Speed improvements, - ProjectTypeGuids for unity-generated project - Improve UI for Project Generation settings - Changes in csc.rsp would cause project-generation - Remove NoWarn 0169 from generated csproj - Support custom JetBrains Toolbox installation location ## [1.2.1] - 2019-12-09 - Load optimised EditorPlugin version compiled to net 461, with fallback to previous version. - On ExternalEditor settings page: reorder Generate all ... after Extensions handled - Better presentation for Rider of some version in ExternalEditors list - Initial support for Code Coverage with dotCover plugin in Rider - Added support for Player Project generation ## [1.1.4] - 2019-11-21 - Fix warning - unreachable code ## [1.1.3] - 2019-10-17 - Update External Editor, when new toolbox build was installed - Add xaml to default list of extensions to include in csproj - Avoid initializing Rider package in secondary Unity process, which does Asset processing - Reflect multiple csc.rsp arguments to generated csproj files: https://github.com/JetBrains/resharper-unity/issues/1337 - Setting, which allowed to override LangVersion removed in favor of langversion in csc.rsp - Environment.NewLine is used in generated project files instead of Windows line separator. ## [1.1.2] - 2019-09-18 performance optimizations: - avoid multiple evaluations - avoid reflection in DisableSyncSolutionOnceCallBack - project generation optimization fixes: - avoid compilation error with incompatible `Test Framework` package ## [1.1.1] - 2019-08-26 parse nowarn in csc.rsp warning, when Unity was started from Rider, but external editor was different improved unit test support workaround to avoid Unity internal project-generation (fix #28) ## [1.1.0] - 2019-07-02 new setting to manage list of extensions to be opened with Rider avoid breaking everything on any unhandled exception in RiderScriptEditor cctor hide Rider settings, when different Editor is selected dynamically load only newer rider plugins path detection (work on unix symlinks) speed up for project generation lots of bug fixing ## [1.0.8] - 2019-05-20 Fix NullReferenceException when External editor was pointing to non-existing Rider everything was broken by null-ref. ## [1.0.7] - 2019-05-16 Initial migration steps from rider plugin to package. Fix OSX check and opening of files. ## [1.0.6] - 2019-04-30 Ensure asset database is refreshed when generating csproj and solution files. ## [1.0.5] - 2019-04-27 Add support for generating all csproj files. ## [1.0.4] - 2019-04-18 Fix relative package paths. Fix opening editor on mac. ## [1.0.3] - 2019-04-12 Fixing null reference issue for callbacks to Asset pipeline. ## [1.0.2] - 2019-01-01 ### This is the first release of *Unity Package rider_editor*. Using the newly created api to integrate Rider with Unity. ================================================ FILE: Packages/com.unity.ide.rider/CHANGELOG.md.meta ================================================ fileFormatVersion: 2 guid: 8645aa9c3c74fb34ba9499e14fb332b5 TextScriptImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/CONTRIBUTING.md ================================================ # Contributing ## All contributions are subject to the [Unity Contribution Agreement(UCA)](https://unity3d.com/legal/licenses/Unity_Contribution_Agreement) By making a pull request, you are confirming agreement to the terms and conditions of the UCA, including that your Contributions are your original creation and that you have complete right and authority to make your Contributions. ## Once you have a change ready following these ground rules. Simply make a pull request ================================================ FILE: Packages/com.unity.ide.rider/CONTRIBUTING.md.meta ================================================ fileFormatVersion: 2 guid: 5e83f8baac96eaa47bdd9ca781cd2002 TextScriptImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Documentation~/README.md ================================================ # Code Editor Package for Rider This package is not intended to be modified by users. Nor does it provide any api intended to be included in user projects. ================================================ FILE: Packages/com.unity.ide.rider/Documentation~/TableOfContents.md ================================================ * [About JetBrains Rider Editor](index.md) * [Using the JetBrains Rider Editor package](using-the-jetbrains-rider-editor-package.md) ================================================ FILE: Packages/com.unity.ide.rider/Documentation~/index.md ================================================ # About JetBrains Rider Editor The JetBrains Rider editor package integrates support for the [JetBrains Rider](https://www.jetbrains.com/rider/) .NET Integrated Development Environment (IDE), into the Unity Editor. This package provides an end-point for Rider to call different Unity APIs and to generate .csproj and .sln files, which Rider uses to implement support for Unity in its [plug-in](https://github.com/JetBrains/resharper-unity). This package ensures that IDE features like autocomplete suggestions and flagging dependency conflicts work in Rider. It uses .cproj and .sln files which store information about your project such as: * Versioning information * Build files * Platform requirements * Web server or database settings Not all code in Unity is directly visible to code editors, particularly when using packages. This is because packages don’t provide their own .csproj files, and Unity doesn’t create them for installed packages by default. This means that IDE features like autocomplete suggestions and flagging dependency conflicts do not work with code in these packages. The purpose of this package is to produce the .csproj files that make these features possible by default when you use Rider. ## Installation As of Unity version 2019.2, this package comes as a part of the default Unity installation. If you are updating your project from an older version of Unity, you might need to install this package via the Package Manager. ## Requirements This version of the JetBrains Rider editor package is compatible with the following versions of the Unity Editor: * 2019.2.6 or later To use this package, you must have the following third-party products installed: * JetBrains Rider version 2019.3 or newer For more information about the Rider IDE, see the [JetBrains Rider documentation](https://www.jetbrains.com/rider/documentation/). ### Submitting issues This package is maintained by JetBrains and Unity. Submit issues to the [JetBrains/resharper-unity/issues GitHub page](https://github.com/JetBrains/resharper-unity/issues). Unity intends for this package to become accessible to the public on GitHub in the future. ================================================ FILE: Packages/com.unity.ide.rider/Documentation~/using-the-jetbrains-rider-editor-package.md ================================================ # Using the JetBrains Rider Editor package To use the package, go to **Edit > Preferences > External Tools**, click on the **External Script Editor** dropdown menu and select your version of **Rider**. When you select this option, the window reloads. After the window reloads, new settings that control production of .csproj files become available. ![](images/preferences-settings-external-tools-window.png)
*External Tools tab in the Preferences window* ## Commandline endpoints Q: Generate sln/csproj files for CI? A: `Unity -batchmode -quit -projectPath ProjectPath -executeMethod Packages.Rider.Editor.RiderScriptEditor.SyncSolution` Q: Generate sln/csproj and open External Editor? A: `Unity -batchmode -quit -projectPath ProjectPath -executeMethod Packages.Rider.Editor.RiderScriptEditor.SyncSolutionAndOpenExternalEditor` ## Package preferences |**Property:** |**Description:** | |:---|:---| |__Extensions handled__| This field lists the file extensions that open in JetBrains Rider. This field contains a variety of extensions by default. | |__Generate .csproj files for:__ | Each setting in this list enables or disables production of .csproj files for a different type of package. The **Regenerate project files** button updates existing .csproj files and creates the necessary new ones based on the settings you choose. These settings control whether to generate .csproj files for any installed packages. For more information on how to install packages, see the [Adding and removing packages documentation](https://docs.unity3d.com/Manual/upm-ui-actions.html). | |__    Embedded     packages__ | Any package that appears under your project’s Packages folder is an embedded package. An embedded package is not necessarily built-in; you can create your own packages and embed them inside your project. This setting is enabled by default. For more information on embedded packages, see the [Embedded dependencies documentation](https://docs.unity3d.com/Manual/upm-embed.html). | |__    Local     packages__ | Any package that you install from a local repository stored on your machine, but from outside of your Unity project. This setting is enabled by default. | |__    Registry     packages__ | Any package that you install from either the official Unity registry or a custom registry. Packages in the Unity registry are available to install directly from the Package Manager. For more information about the Unity package registry, see the [Package Registry section of the Unity Package Manager documentation](https://docs.unity3d.com/Packages/com.unity.package-manager-ui@1.8/manual/index.html#PackManRegistry). For information on creating and using custom registries in addition to the Unity registry, see the [Scoped package registries documentation](https://docs.unity3d.com/Manual/upm-scoped.html). | |__    Git     packages__ | Any package you install directly from a Git repository using a URL. | |__    Built-in     packages__ | Any package that is already installed as part of the default Unity installation. | |__    Tarball     packages__ | Any package you install from a GZip tarball archive on the local machine, outside of your Unity project. | |__    Unknown     packages__ | Any package which Unity cannot determine an origin for. This could be because the package doesn’t list its origin, or that Unity doesn’t recognize the origin listed. | |__Player projects__ | For each player project, generate an additional .csproj file named 'originalProjectName.Player.csproj'. This allows different project types to have their code included in Rider’s systems, such as assembly definitions or testing suites. | This package also adds a second tab under **Preferences** named **Rider**, pictured below. ![](images/preferences-rider-tab.png)
*Rider tab in the Preferences window* > [!NOTE] > The Logging Level menu does not control the level of Unity's logging, only the level of log messages that Rider package logs in its own log file. For more information on controlling Unity's logging level, see the [Stack Trace Logging section of the Console Window documentation](https://docs.unity3d.com/Manual/Console.html#StackTraceLogging). |**Property:** |**Description:** | |:---|:---| |__**Pass Console to Rider**__| If **Pass Console to Rider** is enabled, Rider can access data that Unity sends to the Unity Console and display it within its own environment instead. | |__**Log file**__ | The **Log file** field contains an **Open log** button. Select this button to open the log file inside the Rider IDE. This button is unavailable when **Logging Level** is set to **OFF**. | |__Logging Level__ | The **Logging Level** menu controls how detailed are the Rider package logs. Those logs may be used for troubleshooting communication between Rider and Unity. Rider package logs all messages of the type you select as well as any messages of a more severe type. For example, if you choose **WARN**, then Rider logs all **ERROR** and **FATAL** messages as well as **WARN** messages. The message types are listed below in order of severity, with **FATAL** as the most severe type of message and **TRACE** as the least severe. | |__**OFF**__ | Rider does not produce any logs. | |__**FATAL__ | Logs information relating to serious problems that cause the application to crash. This setting produces the smallest logs. | |__**ERROR**__ | Logs information about errors that prevent some functionality from working, but don’t cause the application to fail (for example, a failed database connection). | |__**WARN**__ | Logs information about possible problems, or any unusual behaviour. Warnings don’t indicate that something has gone wrong, but that Unity detects something that might potentially cause an issue if not investigated. | |__**INFO**__ | Logs information about normal operation of the application, such as a successful database connection attempt. | |__**VERBOSE**__ | Logs detailed but not exhaustive information about your code. This setting is helpful for checking how your code executes or providing diagnostic information for other developers. | |__**TRACE**__ | Logs as much information about the application as possible. This can create a very large and detailed log, so it’s good practice to only use it when attempting to find the cause of a specific issue with your code. | ================================================ FILE: Packages/com.unity.ide.rider/LICENSE.md ================================================ MIT License Copyright (c) 2019 Unity Technologies Copyright (c) 2019 JetBrains s.r.o. All rights reserved. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Packages/com.unity.ide.rider/LICENSE.md.meta ================================================ fileFormatVersion: 2 guid: 5598b14661b5f4c43bed757f34b6d172 TextScriptImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Discovery.cs ================================================ using System; using System.Linq; using JetBrains.Rider.PathLocator; using Packages.Rider.Editor.Util; using Unity.CodeEditor; namespace Packages.Rider.Editor { internal interface IDiscovery { CodeEditor.Installation[] PathCallback(); } internal class Discovery : IDiscovery { public static readonly RiderPathLocator RiderPathLocator; public static readonly RiderFileOpener RiderFileOpener; static Discovery() { var env = new RiderLocatorEnvironment(); RiderPathLocator = new RiderPathLocator(env); RiderFileOpener = new RiderFileOpener(env); } public CodeEditor.Installation[] PathCallback() { // still we want to search for installations, when Preferences is opened var res = RiderPathLocator.GetAllRiderPaths() .Select(riderInfo => new CodeEditor.Installation { Path = riderInfo.Path, Name = riderInfo.Presentation }) .ToList(); var editorPath = RiderScriptEditor.CurrentEditor; if (RiderScriptEditor.IsRiderOrFleetInstallation(editorPath) && !res.Any(a => a.Path == editorPath) && FileSystemUtil.EditorPathExists(editorPath)) { // External editor manually set from custom location var info = new RiderPathLocator.RiderInfo(RiderPathLocator, editorPath, false); var installation = new CodeEditor.Installation { Path = info.Path, Name = info.Presentation }; res.Add(installation); } return res.ToArray(); } } internal class RiderLocatorEnvironment : IRiderLocatorEnvironment { public OS CurrentOS { get { switch (UnityEngine.SystemInfo.operatingSystemFamily) { case UnityEngine.OperatingSystemFamily.Windows: return OS.Windows; case UnityEngine.OperatingSystemFamily.MacOSX: return OS.MacOSX; case UnityEngine.OperatingSystemFamily.Linux: return OS.Linux; default: return OS.Other; } } } public T FromJson(string json) { return (T)UnityEngine.JsonUtility.FromJson(json, typeof(T)); } public void Verbose(string message, Exception e = null) { // only writes to Editor.log Console.WriteLine(message); if (e != null) Console.WriteLine(e); } public void Info(string message, Exception e = null) { UnityEngine.Debug.Log(message); if (e != null) UnityEngine.Debug.Log(e); } public void Warn(string message, Exception e = null) { UnityEngine.Debug.LogWarning(message); if (e != null) UnityEngine.Debug.LogWarning(e); } public void Error(string message, Exception e = null) { UnityEngine.Debug.LogError(message); if (e != null) UnityEngine.Debug.LogException(e); } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Discovery.cs.meta ================================================ fileFormatVersion: 2 guid: dab656c79e1985c40b31faebcda44442 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/EditorPluginInterop.cs ================================================ using System; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using Debug = UnityEngine.Debug; namespace Packages.Rider.Editor { internal static class EditorPluginInterop { private static string EditorPluginAssemblyNamePrefix = "JetBrains.Rider.Unity.Editor.Plugin."; public static readonly string EditorPluginAssemblyName = $"{EditorPluginAssemblyNamePrefix}Net46.Repacked"; public static readonly string EditorPluginAssemblyNameFallback = $"{EditorPluginAssemblyNamePrefix}Full.Repacked"; private static string ourEntryPointTypeName = "JetBrains.Rider.Unity.Editor.PluginEntryPoint"; private static Assembly ourEditorPluginAssembly; public static Assembly EditorPluginAssembly { get { if (ourEditorPluginAssembly != null) return ourEditorPluginAssembly; var assemblies = AppDomain.CurrentDomain.GetAssemblies(); ourEditorPluginAssembly = assemblies.FirstOrDefault(a => { try { return a.GetName().Name.StartsWith(EditorPluginAssemblyNamePrefix); // some user assemblies may fail here } catch (Exception) { // ignored } return default; }); return ourEditorPluginAssembly; } } private static void DisableSyncSolutionOnceCallBack() { // RiderScriptableSingleton.Instance.CsprojProcessedOnce = true; // Otherwise EditorPlugin regenerates all on every AppDomain reload var assembly = EditorPluginAssembly; if (assembly == null) return; var type = assembly.GetType("JetBrains.Rider.Unity.Editor.Utils.RiderScriptableSingleton"); if (type == null) return; var baseType = type.BaseType; if (baseType == null) return; var instance = baseType.GetProperty("Instance"); if (instance == null) return; var instanceVal = instance.GetValue(null); var member = type.GetProperty("CsprojProcessedOnce"); if (member==null) return; member.SetValue(instanceVal, true); } public static string LogPath { get { try { var assembly = EditorPluginAssembly; if (assembly == null) return null; var type = assembly.GetType(ourEntryPointTypeName); if (type == null) return null; var field = type.GetField("LogPath", BindingFlags.NonPublic | BindingFlags.Static); if (field == null) return null; return field.GetValue(null) as string; } catch (Exception) { Debug.Log("Unable to do OpenFile to Rider from dll, fallback to com.unity.ide.rider implementation."); } return null; } } public static bool OpenFileDllImplementation(string path, int line, int column) { var openResult = false; // reflection for fast OpenFileLineCol, when Rider is started and protocol connection is established try { var assembly = EditorPluginAssembly; if (assembly == null) return false; var type = assembly.GetType(ourEntryPointTypeName); if (type == null) return false; var field = type.GetField("OpenAssetHandler", BindingFlags.NonPublic | BindingFlags.Static); if (field == null) return false; var handlerInstance = field.GetValue(null); var method = handlerInstance.GetType() .GetMethod("OnOpenedAsset", new[] {typeof(string), typeof(int), typeof(int)}); if (method == null) return false; var assetFilePath = path; if (!string.IsNullOrEmpty(path)) assetFilePath = Path.GetFullPath(path); openResult = (bool) method.Invoke(handlerInstance, new object[] {assetFilePath, line, column}); } catch (Exception e) { Debug.Log("Unable to do OpenFile to Rider from dll, fallback to com.unity.ide.rider implementation."); Debug.LogException(e); } return openResult; } public static bool EditorPluginIsLoadedFromAssets(Assembly assembly) { if (assembly == null) return false; var location = assembly.Location; var currentDir = Directory.GetCurrentDirectory(); return location.StartsWith(currentDir, StringComparison.InvariantCultureIgnoreCase); } internal static void InitEntryPoint(Assembly assembly) { try { var version = RiderScriptEditorData.instance.editorBuildNumber; if (version != null) { if (version.Major < 192) DisableSyncSolutionOnceCallBack(); // is require for Rider prior to 2019.2 } else DisableSyncSolutionOnceCallBack(); var type = assembly.GetType("JetBrains.Rider.Unity.Editor.AfterUnity56.EntryPoint"); if (type == null) type = assembly.GetType("JetBrains.Rider.Unity.Editor.UnitTesting.EntryPoint"); // oldRider RuntimeHelpers.RunClassConstructor(type.TypeHandle); } catch (TypeInitializationException ex) { Debug.LogException(ex); if (ex.InnerException != null) Debug.LogException(ex.InnerException); } } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/EditorPluginInterop.cs.meta ================================================ fileFormatVersion: 2 guid: f9bd02a3a916be64c9b47b1305149423 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/JetBrains.Rider.PathLocator.dll.meta ================================================ fileFormatVersion: 2 guid: ea3ec41b33345cd4f9298a51abdaa198 PluginImporter: externalObjects: {} serializedVersion: 2 iconMap: {} executionOrder: {} defineConstraints: [] isPreloaded: 0 isOverridable: 1 isExplicitlyReferenced: 1 validateReferences: 0 platformData: - first: : Any second: enabled: 0 settings: Exclude Editor: 0 Exclude Linux64: 1 Exclude OSXUniversal: 1 Exclude Win: 1 Exclude Win64: 1 - first: Any: second: enabled: 0 settings: {} - first: Editor: Editor second: enabled: 1 settings: CPU: AnyCPU DefaultValueInitialized: true OS: AnyOS - first: Standalone: Linux64 second: enabled: 0 settings: CPU: AnyCPU - first: Standalone: OSXUniversal second: enabled: 0 settings: CPU: None - first: Standalone: Win second: enabled: 0 settings: CPU: x86 - first: Standalone: Win64 second: enabled: 0 settings: CPU: x86_64 - first: Windows Store Apps: WindowsStoreApps second: enabled: 0 settings: CPU: AnyCPU userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/LoggingLevel.cs ================================================ namespace Packages.Rider.Editor { internal enum LoggingLevel { /// /// Do not use it in logging. Only in config to disable logging. /// OFF, /// For errors that lead to application failure FATAL, /// For errors that must be shown in Exception Browser ERROR, /// Suspicious situations but not errors WARN, /// Regular level for important events INFO, /// Additional info for debbuging VERBOSE, /// Methods & callstacks tracing, more than verbose TRACE, } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/LoggingLevel.cs.meta ================================================ fileFormatVersion: 2 guid: 71bb46b59a9a7a346bbab1e185c723df MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/PluginSettings.cs ================================================ using UnityEditor; using UnityEngine; namespace Packages.Rider.Editor { internal static class PluginSettings { public static LoggingLevel SelectedLoggingLevel { get => (LoggingLevel) EditorPrefs.GetInt("Rider_SelectedLoggingLevel", 0); private set => EditorPrefs.SetInt("Rider_SelectedLoggingLevel", (int) value); } public static bool LogEventsCollectorEnabled { get => EditorPrefs.GetBool("Rider_LogEventsCollectorEnabled", true); private set => EditorPrefs.SetBool("Rider_LogEventsCollectorEnabled", value); } /// /// Preferences menu layout /// /// /// Contains all 3 toggles: Enable/Disable; Debug On/Off; Writing Launch File On/Off /// [SettingsProvider] private static SettingsProvider RiderPreferencesItem() { if (!RiderScriptEditor.IsRiderOrFleetInstallation(RiderScriptEditor.CurrentEditor)) return null; if (!RiderScriptEditorData.instance.shouldLoadEditorPlugin) return null; var provider = new SettingsProvider("Preferences/Rider", SettingsScope.User) { label = "Rider", keywords = new[] { "Rider" }, guiHandler = (searchContext) => { EditorGUIUtility.labelWidth = 200f; EditorGUILayout.BeginVertical(); GUILayout.BeginVertical(); LogEventsCollectorEnabled = EditorGUILayout.Toggle(new GUIContent("Pass Console to Rider:"), LogEventsCollectorEnabled); GUILayout.EndVertical(); GUILayout.Label(""); if (!string.IsNullOrEmpty(EditorPluginInterop.LogPath)) { EditorGUILayout.BeginHorizontal(); EditorGUILayout.PrefixLabel("Log file:"); var previous = GUI.enabled; GUI.enabled = previous && SelectedLoggingLevel != LoggingLevel.OFF; var button = GUILayout.Button(new GUIContent("Open log")); if (button) { // would use Rider if `log` is on the list of extensions, otherwise would use editor configured in the OS UnityEditorInternal.InternalEditorUtility.OpenFileAtLineExternal(EditorPluginInterop.LogPath, 0); } GUI.enabled = previous; GUILayout.EndHorizontal(); } var loggingMsg = @"Sets the amount of Rider Debug output. If you are about to report an issue, please select Verbose logging level and attach Unity console output to the issue."; SelectedLoggingLevel = (LoggingLevel) EditorGUILayout.EnumPopup(new GUIContent("Logging Level:", loggingMsg), SelectedLoggingLevel); EditorGUILayout.HelpBox(loggingMsg, MessageType.None); const string url = "https://github.com/JetBrains/resharper-unity"; if (LinkButton(url)) Application.OpenURL(url);; GUILayout.FlexibleSpace(); GUILayout.BeginHorizontal(); GUILayout.FlexibleSpace(); var assembly = EditorPluginInterop.EditorPluginAssembly; if (assembly != null) { var version = assembly.GetName().Version; GUILayout.Label("Plugin version: " + version, new GUIStyle(GUI.skin.label) { margin = new RectOffset(4, 4, 4, 4), }); } GUILayout.EndHorizontal(); EditorGUILayout.EndVertical(); } }; return provider; } public static bool LinkButton(string url) { var bClicked = GUILayout.Button(url, RiderStyles.LinkLabelStyle); var rect = GUILayoutUtility.GetLastRect(); rect.width = RiderStyles.LinkLabelStyle.CalcSize(new GUIContent(url)).x; EditorGUIUtility.AddCursorRect(rect, MouseCursor.Link); return bClicked; } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/PluginSettings.cs.meta ================================================ fileFormatVersion: 2 guid: 1bfe12aa306c0c74db4f4f1a1a0ae5ce MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/PostProcessors/RiderAssetPostprocessor.cs ================================================ using Unity.CodeEditor; using UnityEditor; namespace Packages.Rider.Editor.PostProcessors { internal class RiderAssetPostprocessor: AssetPostprocessor { public static bool OnPreGeneratingCSProjectFiles() { var path = RiderScriptEditor.GetEditorRealPath(CodeEditor.CurrentEditorInstallation); if (RiderScriptEditor.IsRiderOrFleetInstallation(path)) return !ProjectGeneration.ProjectGeneration.isRiderProjectGeneration; return false; } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/PostProcessors/RiderAssetPostprocessor.cs.meta ================================================ fileFormatVersion: 2 guid: 45471ad7b8c1f964da5e3c07d57fbf4f MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/PostProcessors.meta ================================================ fileFormatVersion: 2 guid: aa290bd9a165a0543a4bf85ac73914bc folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/AssemblyNameProvider.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEditor.Compilation; using UnityEditor.PackageManager; using PackageInfo = UnityEditor.PackageManager.PackageInfo; namespace Packages.Rider.Editor.ProjectGeneration { internal class AssemblyNameProvider : IAssemblyNameProvider { private readonly Dictionary m_PackageInfoCache = new Dictionary(); private readonly Dictionary m_ResponseFilesCache = new Dictionary(); private readonly string[] _specialPackagesForProjectGen = new[] { "com.unity.entities", "com.unity.collections" }; ProjectGenerationFlag m_ProjectGenerationFlag = (ProjectGenerationFlag)EditorPrefs.GetInt("unity_project_generation_flag", 3); public string[] ProjectSupportedExtensions => EditorSettings.projectGenerationUserExtensions; public string ProjectGenerationRootNamespace => EditorSettings.projectGenerationRootNamespace; private Assembly[] m_AllEditorAssemblies; private Assembly[] m_AllPlayerAssemblies; private Assembly[] m_AllAssemblies; public ProjectGenerationFlag ProjectGenerationFlag { get => m_ProjectGenerationFlag; private set { EditorPrefs.SetInt("unity_project_generation_flag", (int)value); m_ProjectGenerationFlag = value; } } public string GetAssemblyNameFromScriptPath(string path) { return CompilationPipeline.GetAssemblyNameFromScriptPath(path); } public Assembly[] GetAllAssemblies() { if (m_AllEditorAssemblies == null) { m_AllEditorAssemblies = GetAssembliesByType(AssembliesType.Editor); m_AllAssemblies = m_AllEditorAssemblies; } if (ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.PlayerAssemblies)) { if (m_AllPlayerAssemblies == null) { m_AllPlayerAssemblies = GetAssembliesByType(AssembliesType.Player); m_AllAssemblies = new Assembly[m_AllEditorAssemblies.Length + m_AllPlayerAssemblies.Length]; Array.Copy(m_AllEditorAssemblies, m_AllAssemblies, m_AllEditorAssemblies.Length); Array.Copy(m_AllPlayerAssemblies, 0, m_AllAssemblies, m_AllEditorAssemblies.Length, m_AllPlayerAssemblies.Length); } } return m_AllAssemblies; } private static Assembly[] GetAssembliesByType(AssembliesType type) { // This is a very expensive Unity call... var compilationPipelineAssemblies = CompilationPipeline.GetAssemblies(type); var assemblies = new Assembly[compilationPipelineAssemblies.Length]; var i = 0; foreach (var compilationPipelineAssembly in compilationPipelineAssemblies) { // The CompilationPipeline's assemblies have an output path of Libraries/ScriptAssemblies // TODO: It might be worth using the app's copy of Assembly and updating output path when we need it // But that requires tracking editor and player assemblies separately var outputPath = type == AssembliesType.Editor ? $@"Temp\Bin\Debug\{compilationPipelineAssembly.name}\" : $@"Temp\Bin\Debug\{compilationPipelineAssembly.name}\Player\"; assemblies[i] = new Assembly( compilationPipelineAssembly.name, outputPath, compilationPipelineAssembly.sourceFiles, compilationPipelineAssembly.defines, compilationPipelineAssembly.assemblyReferences, compilationPipelineAssembly.compiledAssemblyReferences, compilationPipelineAssembly.flags, compilationPipelineAssembly.compilerOptions #if UNITY_2020_2_OR_NEWER , compilationPipelineAssembly.rootNamespace #endif ); i++; } return assemblies; } public Assembly GetNamedAssembly(string name) { foreach (var assembly in GetAllAssemblies()) { if (assembly.name == name) return assembly; } return null; } public string GetProjectName(string name, string[] defines) { if (!ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.PlayerAssemblies)) return name; return !defines.Contains("UNITY_EDITOR") ? name + ".Player" : name; } public IEnumerable GetAllAssetPaths() { return AssetDatabase.GetAllAssetPaths(); } private static string GetPackageRootDirectoryName(string assetPath) { const string packagesPrefix = "packages/"; if (!assetPath.StartsWith(packagesPrefix, StringComparison.OrdinalIgnoreCase)) { return null; } var followupSeparator = assetPath.IndexOf('/', packagesPrefix.Length); // Note that we return the first path segment without modifying/normalising case! return followupSeparator == -1 ? assetPath : assetPath.Substring(0, followupSeparator); } public PackageInfo GetPackageInfoForAssetPath(string assetPath) { var packageName = GetPackageRootDirectoryName(assetPath); if (packageName == null) { return null; } // Assume the package name casing is consistent. If it's not, we'll fall back to an uppercase variant that's // saved in the same dictionary. This gives us cheaper case sensitive matching, with a fallback if our assumption // is incorrect if (m_PackageInfoCache.TryGetValue(packageName, out var cachedPackageInfo)) return cachedPackageInfo; var packageNameUpper = packageName.ToUpperInvariant(); if (m_PackageInfoCache.TryGetValue(packageNameUpper, out cachedPackageInfo)) return cachedPackageInfo; var result = PackageInfo.FindForAssetPath(packageName); m_PackageInfoCache[packageName] = result; m_PackageInfoCache[packageNameUpper] = result; return result; } public void ResetCaches() { m_PackageInfoCache.Clear(); m_ResponseFilesCache.Clear(); m_AllEditorAssemblies = null; m_AllPlayerAssemblies = null; } public bool IsInternalizedPackagePath(string path) { if (string.IsNullOrEmpty(path)) { return false; } var packageInfo = GetPackageInfoForAssetPath(path); if (packageInfo == null) { return false; } if (ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.PlayerAssemblies) && _specialPackagesForProjectGen.Contains(packageInfo.name)) { // special case for RIDER-104519 Rider is reporting errors in scripts that work fine in Unity when utilizing DOTS // it would be better to only generate .Player projects and not Editor ones, but that would require big changes in ProjectGeneration return false; } var packageSource = packageInfo.source; switch (packageSource) { case PackageSource.Embedded: return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.Embedded); case PackageSource.Registry: return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.Registry); case PackageSource.BuiltIn: return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.BuiltIn); case PackageSource.Unknown: return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.Unknown); case PackageSource.Local: return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.Local); case PackageSource.Git: return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.Git); #if UNITY_2019_3_OR_NEWER case PackageSource.LocalTarball: return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.LocalTarBall); #endif } return false; } public ResponseFileData ParseResponseFile(string responseFilePath, string projectDirectory, ApiCompatibilityLevel apiCompatibilityLevel) { var key = responseFilePath + ":" + (int) apiCompatibilityLevel; if (!m_ResponseFilesCache.TryGetValue(key, out var responseFileData)) { var systemReferenceDirectories = CompilationPipeline.GetSystemAssemblyDirectories(apiCompatibilityLevel); responseFileData = CompilationPipeline.ParseResponseFile( responseFilePath, projectDirectory, systemReferenceDirectories ); m_ResponseFilesCache.Add(key, responseFileData); } return responseFileData; } public IEnumerable GetRoslynAnalyzerPaths() { return PluginImporter.GetAllImporters() .Where(i => !i.isNativePlugin && AssetDatabase.GetLabels(i).SingleOrDefault(l => l == "RoslynAnalyzer") != null) .Select(i => i.assetPath); } public void ToggleProjectGeneration(ProjectGenerationFlag preference) { if (ProjectGenerationFlag.HasFlag(preference)) { ProjectGenerationFlag ^= preference; } else { ProjectGenerationFlag |= preference; } } public void ResetProjectGenerationFlag() { ProjectGenerationFlag = ProjectGenerationFlag.None; } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/AssemblyNameProvider.cs.meta ================================================ fileFormatVersion: 2 guid: 56c8c6e437b14f8e9a91e9832c11bc1a timeCreated: 1580717719 ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/FileIOProvider.cs ================================================ using System; using System.IO; using System.Security; using System.Text; using Packages.Rider.Editor.Util; namespace Packages.Rider.Editor.ProjectGeneration { class FileIOProvider : IFileIO { public bool Exists(string path) { return File.Exists(path); } public TextReader GetReader(string path) { return new StreamReader(path); } public string ReadAllText(string path) { return File.ReadAllText(path); } public void WriteAllText(string path, string content) { File.WriteAllText(path, content, Encoding.UTF8); LastWriteTracker.UpdateLastWriteIfNeeded(path); } public string EscapedRelativePathFor(string file, string rootDirectoryFullPath) { // We have to normalize the path, because the PackageManagerRemapper assumes // dir seperators will be os specific. var absolutePath = Path.GetFullPath(file.NormalizePath()); var path = SkipPathPrefix(absolutePath, rootDirectoryFullPath); return SecurityElement.Escape(path); } private static string SkipPathPrefix(string path, string prefix) { var root = prefix[prefix.Length - 1] == Path.DirectorySeparatorChar ? prefix : prefix + Path.DirectorySeparatorChar; return path.StartsWith(root, StringComparison.Ordinal) ? path.Substring(root.Length) : path; } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/FileIOProvider.cs.meta ================================================ fileFormatVersion: 2 guid: a6ba838b1348d5e46a7eaacd1646c1d3 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/GUIDProvider.cs ================================================ namespace Packages.Rider.Editor.ProjectGeneration { class GUIDProvider : IGUIDGenerator { public string ProjectGuid(string name) { return SolutionGuidGenerator.GuidForProject(name); } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/GUIDProvider.cs.meta ================================================ fileFormatVersion: 2 guid: 8cfde1a59fb35574189691a9de1df93b MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/IAssemblyNameProvider.cs ================================================ using System; using System.Collections.Generic; using UnityEditor; using UnityEditor.Compilation; namespace Packages.Rider.Editor.ProjectGeneration { internal interface IAssemblyNameProvider { string[] ProjectSupportedExtensions { get; } string ProjectGenerationRootNamespace { get; } ProjectGenerationFlag ProjectGenerationFlag { get; } string GetAssemblyNameFromScriptPath(string path); string GetProjectName(string name, string[] defines); bool IsInternalizedPackagePath(string path); Assembly[] GetAllAssemblies(); Assembly GetNamedAssembly(string name); IEnumerable GetAllAssetPaths(); UnityEditor.PackageManager.PackageInfo GetPackageInfoForAssetPath(string assetPath); ResponseFileData ParseResponseFile(string responseFilePath, string projectDirectory, ApiCompatibilityLevel systemReferenceDirectories); IEnumerable GetRoslynAnalyzerPaths(); void ToggleProjectGeneration(ProjectGenerationFlag preference); void ResetCaches(); } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/IAssemblyNameProvider.cs.meta ================================================ fileFormatVersion: 2 guid: 5eea837708474d7e9c1cb4b2eca0213f timeCreated: 1580717710 ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/IFileIO.cs ================================================ using System.IO; namespace Packages.Rider.Editor.ProjectGeneration { internal interface IFileIO { bool Exists(string path); TextReader GetReader(string path); string ReadAllText(string path); void WriteAllText(string path, string content); // rootDirectoryFullPath is assumed to be the result of Path.GetFullPath // Passing the directory with a trailing slash (Path.DirectorySeparatorChar) will avoid an allocation string EscapedRelativePathFor(string path, string rootDirectoryFullPath); } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/IFileIO.cs.meta ================================================ fileFormatVersion: 2 guid: 1bdab5b8331b4506bf4eae379235c053 timeCreated: 1580717666 ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/IGUIDGenerator.cs ================================================ namespace Packages.Rider.Editor.ProjectGeneration { internal interface IGUIDGenerator { string ProjectGuid(string name); } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/IGUIDGenerator.cs.meta ================================================ fileFormatVersion: 2 guid: 8c7e8e301f8b4c30bbf9db502d637f0f timeCreated: 1580717700 ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/IGenerator.cs ================================================ using System.Collections.Generic; namespace Packages.Rider.Editor.ProjectGeneration { internal interface IGenerator { bool SyncIfNeeded(IEnumerable affectedFiles, IEnumerable reimportedFiles, bool checkProjectFiles = false); void Sync(); bool HasSolutionBeenGenerated(); string SolutionFile(); IAssemblyNameProvider AssemblyNameProvider { get; } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/IGenerator.cs.meta ================================================ fileFormatVersion: 2 guid: 39cb9ab8a3b2452cbf58ffbea841d203 timeCreated: 1580717654 ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/LastWriteTracker.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; namespace Packages.Rider.Editor.ProjectGeneration { internal static class LastWriteTracker { internal static bool HasLastWriteTimeChanged() { if (!IsUnityCompatible()) return false; // any external changes of sln/csproj should cause their regeneration // Directory.GetCurrentDirectory(), "*.csproj", "*.sln" var files = new List(); var directoryInfo = new DirectoryInfo(Directory.GetCurrentDirectory()); files.AddRange(directoryInfo.GetFiles("*.csproj")); files.Add(new FileInfo(Path.Combine(directoryInfo.FullName, directoryInfo.Name + ".sln"))); return files.Any(a => a.LastWriteTime > RiderScriptEditorPersistedState.instance.LastWrite); } internal static void UpdateLastWriteIfNeeded(string path) { if (!IsUnityCompatible()) return; var fileInfo = new FileInfo(path); if (fileInfo.Directory == null) return; var directoryInfo = new DirectoryInfo(Directory.GetCurrentDirectory()); if (fileInfo.Directory.FullName.Equals(directoryInfo.FullName, StringComparison.OrdinalIgnoreCase) && (fileInfo.Extension.Equals(".csproj", StringComparison.OrdinalIgnoreCase) || fileInfo.Name.Equals(directoryInfo.Name + ".sln", StringComparison.OrdinalIgnoreCase))) { RiderScriptEditorPersistedState.instance.LastWrite = fileInfo.LastWriteTime; } } internal static bool IsUnityCompatible() { #if UNITY_2020_1_OR_NEWER return true; #else return false; #endif } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/LastWriteTracker.cs.meta ================================================ fileFormatVersion: 2 guid: 7019e230344c48d4b81602e2e978e5de timeCreated: 1645608955 ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/PackageManagerTracker.cs ================================================ using System.IO; #if UNITY_2020_1_OR_NEWER using UnityEditor.PackageManager; #endif namespace Packages.Rider.Editor.ProjectGeneration { internal static class PackageManagerTracker { private static bool HasManifestJsonLastWriteTimeChanged() { if (!LastWriteTracker.IsUnityCompatible()) return false; var directoryInfo = new DirectoryInfo(Directory.GetCurrentDirectory()); var manifestFile = new FileInfo(Path.Combine(directoryInfo.FullName, "Packages/manifest.json")); if (manifestFile.Exists) { // for the manifest.json, we store the LastWriteTime here var res = manifestFile.LastWriteTime > RiderScriptEditorPersistedState.instance.ManifestJsonLastWrite; if (res) RiderScriptEditorPersistedState.instance.ManifestJsonLastWrite = manifestFile.LastWriteTime; return res; } return false; } /// /// If the manifest.json was changed outside Unity and Rider calls Unity to Refresh, we should call PM to Refresh its state also /// /// internal static void SyncIfNeeded(bool checkProjectFiles) { #if UNITY_2020_1_OR_NEWER if (checkProjectFiles && HasManifestJsonLastWriteTimeChanged()) { Client.Resolve(); } #endif } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/PackageManagerTracker.cs.meta ================================================ fileFormatVersion: 2 guid: 32e50e97779a4753a461190076119a99 timeCreated: 1676882821 ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/ProjectGeneration.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Security; using System.Text; using Packages.Rider.Editor.Util; using UnityEditor; using UnityEditor.Compilation; using UnityEngine; namespace Packages.Rider.Editor.ProjectGeneration { internal class ProjectGeneration : IGenerator { private enum ScriptingLanguage { None, CSharp } /// /// Map source extensions to ScriptingLanguages /// private static readonly Dictionary k_BuiltinSupportedExtensions = new Dictionary { { ".cs", ScriptingLanguage.CSharp }, { ".uxml", ScriptingLanguage.None }, { ".uss", ScriptingLanguage.None }, { ".shader", ScriptingLanguage.None }, { ".compute", ScriptingLanguage.None }, { ".cginc", ScriptingLanguage.None }, { ".hlsl", ScriptingLanguage.None }, { ".glslinc", ScriptingLanguage.None }, { ".template", ScriptingLanguage.None }, { ".raytrace", ScriptingLanguage.None }, { ".json", ScriptingLanguage.None}, { ".rsp", ScriptingLanguage.None}, { ".asmdef", ScriptingLanguage.None}, { ".asmref", ScriptingLanguage.None}, { ".xaml", ScriptingLanguage.None}, { ".tt", ScriptingLanguage.None}, { ".t4", ScriptingLanguage.None}, { ".ttinclude", ScriptingLanguage.None} }; private string[] m_ProjectSupportedExtensions = Array.Empty(); // Note that ProjectDirectory can be assumed to be the result of Path.GetFullPath public string ProjectDirectory { get; } public string ProjectDirectoryWithSlash { get; } private readonly string m_ProjectName; private readonly IAssemblyNameProvider m_AssemblyNameProvider; private readonly IFileIO m_FileIOProvider; private readonly IGUIDGenerator m_GUIDGenerator; private readonly Dictionary m_ProjectGuids = new Dictionary(); // If we have multiple projects, the same assembly references are reused for each. Caching the normalised paths and // names is actually cheaper than recalculating each time, in terms of both time and memory allocations private readonly Dictionary m_NormalisedPaths = new Dictionary(); private readonly Dictionary m_AssemblyNames = new Dictionary(); internal static bool isRiderProjectGeneration; // workaround to https://github.cds.internal.unity3d.com/unity/com.unity.ide.rider/issues/28 IAssemblyNameProvider IGenerator.AssemblyNameProvider => m_AssemblyNameProvider; public ProjectGeneration() : this(Directory.GetParent(Application.dataPath).FullName) { } public ProjectGeneration(string projectDirectory) : this(projectDirectory, new AssemblyNameProvider(), new FileIOProvider(), new GUIDProvider()) { } public ProjectGeneration(string projectDirectory, IAssemblyNameProvider assemblyNameProvider, IFileIO fileIoProvider, IGUIDGenerator guidGenerator) { ProjectDirectory = Path.GetFullPath(projectDirectory.NormalizePath()); ProjectDirectoryWithSlash = ProjectDirectory + Path.DirectorySeparatorChar; m_ProjectName = Path.GetFileName(ProjectDirectory); m_AssemblyNameProvider = assemblyNameProvider; m_FileIOProvider = fileIoProvider; m_GUIDGenerator = guidGenerator; } /// /// Syncs the scripting solution if any affected files are relevant. /// /// /// Whether the solution was synced. /// /// /// A set of files whose status has changed /// /// /// A set of files that got reimported /// /// /// Check if project files were changed externally /// public bool SyncIfNeeded(IEnumerable affectedFiles, IEnumerable reimportedFiles, bool checkProjectFiles = false) { SetupSupportedExtensions(); PackageManagerTracker.SyncIfNeeded(checkProjectFiles); if (HasFilesBeenModified(affectedFiles, reimportedFiles) || RiderScriptEditorData.instance.hasChanges || RiderScriptEditorData.instance.HasChangesInCompilationDefines() || (checkProjectFiles && LastWriteTracker.HasLastWriteTimeChanged())) { Sync(); return true; } return false; } private bool HasFilesBeenModified(IEnumerable affectedFiles, IEnumerable reimportedFiles) { return affectedFiles.Any(ShouldFileBePartOfSolution) || reimportedFiles.Any(ShouldSyncOnReimportedAsset); } private static bool ShouldSyncOnReimportedAsset(string asset) { var extension = Path.GetExtension(asset); return extension == ".asmdef" || extension == ".asmref" || Path.GetFileName(asset) == "csc.rsp"; } public void Sync() { SetupSupportedExtensions(); var types = GetAssetPostprocessorTypes(); isRiderProjectGeneration = true; var externalCodeAlreadyGeneratedProjects = OnPreGeneratingCSProjectFiles(types); isRiderProjectGeneration = false; if (!externalCodeAlreadyGeneratedProjects) { GenerateAndWriteSolutionAndProjects(types); } OnGeneratedCSProjectFiles(types); m_AssemblyNameProvider.ResetCaches(); m_AssemblyNames.Clear(); m_NormalisedPaths.Clear(); m_ProjectGuids.Clear(); _buffer = null; RiderScriptEditorData.instance.hasChanges = false; RiderScriptEditorData.instance.InvalidateSavedCompilationDefines(); } public bool HasSolutionBeenGenerated() { return m_FileIOProvider.Exists(SolutionFile()); } private void SetupSupportedExtensions() { var extensions = m_AssemblyNameProvider.ProjectSupportedExtensions; m_ProjectSupportedExtensions = new string[extensions.Length]; for (var i = 0; i < extensions.Length; i++) { m_ProjectSupportedExtensions[i] = "." + extensions[i]; } } private bool ShouldFileBePartOfSolution(string file) { // Exclude files coming from packages except if they are internalized. if (m_AssemblyNameProvider.IsInternalizedPackagePath(file)) { return false; } return HasValidExtension(file); } public bool HasValidExtension(string file) { // Dll's are not scripts but still need to be included.. if (file.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) return true; var extension = Path.GetExtension(file); return IsSupportedExtension(extension); } private bool IsSupportedExtension(string extension) { return k_BuiltinSupportedExtensions.ContainsKey(extension) || m_ProjectSupportedExtensions.Contains(extension); } private class AssemblyUsage { private readonly HashSet m_ProjectAssemblies = new HashSet(); private readonly HashSet m_PrecompiledAssemblies = new HashSet(); public void AddProjectAssembly(Assembly assembly) { m_ProjectAssemblies.Add(assembly.name); } public void AddPrecompiledAssembly(Assembly assembly) { m_PrecompiledAssemblies.Add(assembly.name); } public bool IsProjectAssembly(Assembly assembly) => m_ProjectAssemblies.Contains(assembly.name); public bool IsPrecompiledAssembly(Assembly assembly) => m_PrecompiledAssemblies.Contains(assembly.name); } private void GenerateAndWriteSolutionAndProjects(Type[] types) { // Only synchronize islands that have associated source files and ones that we actually want in the project. // This also filters out DLLs coming from .asmdef files in packages. // Get all of the assemblies that Unity will compile from source. This includes Assembly-CSharp, all user assembly // definitions, and all packages. Not all of the returned assemblies will require project files - by default, // registry, git and local tarball packages are pre-compiled by Unity and will not require a project. This can be // changed by the user in the External Tools settings page. // Each assembly instance contains source files, output path, defines, compiler options and references. There // will be `compiledAssemblyReferences`, which are DLLs, such as UnityEngine.dll, and assembly references, which // are references to other assemblies that Unity will compile from source. Again, these assemblies might be // projects, or pre-compiled by Unity, depending on the options selected by the user. var allAssemblies = m_AssemblyNameProvider.GetAllAssemblies(); var assemblyUsage = new AssemblyUsage(); foreach (var assembly in allAssemblies) { if (assembly.sourceFiles.Any(ShouldFileBePartOfSolution)) assemblyUsage.AddProjectAssembly(assembly); else assemblyUsage.AddPrecompiledAssembly(assembly); } // Get additional assets (other than source files) that we want to add to the projects, e.g. shaders, asmdef, etc. var additionalAssetsByAssembly = GetAdditionalAssets(); var projectParts = new List(); var assemblyNamesWithSource = new HashSet(); foreach (var assembly in allAssemblies) { if (!assemblyUsage.IsProjectAssembly(assembly)) continue; // TODO: Will this check ever be true? Player assemblies don't have the same name as editor assemblies, right? if (assemblyNamesWithSource.Contains(assembly.name)) projectParts.Add(new ProjectPart(assembly.name, assembly, new List())); // do not add asset project parts to both editor and player projects else { additionalAssetsByAssembly.TryGetValue(assembly.name, out var additionalAssetsForProject); projectParts.Add(new ProjectPart(assembly.name, assembly, additionalAssetsForProject)); assemblyNamesWithSource.Add(assembly.name); } } // If there are any assets that should be in a separate assembly, but that assembly folder doesn't contain any // source files, we'll have orphaned assets. Create a project for these assemblies, with references based on the // Rider package assembly // TODO: Would this produce the same results if we removed the check for ShouldFileBePartOfSolution above? // I suspect the only difference would be output path and references, and potentially simplify things var executingAssemblyName = typeof(ProjectGeneration).Assembly.GetName().Name; var riderAssembly = m_AssemblyNameProvider.GetNamedAssembly(executingAssemblyName); string[] coreReferences = null; foreach (var pair in additionalAssetsByAssembly) { var assembly = pair.Key; var additionalAssets = pair.Value; if (!assemblyNamesWithSource.Contains(assembly)) { if (coreReferences == null) { coreReferences = riderAssembly?.compiledAssemblyReferences.Where(a => a.EndsWith("UnityEditor.dll", StringComparison.Ordinal) || a.EndsWith("UnityEngine.dll", StringComparison.Ordinal) || a.EndsWith("UnityEngine.CoreModule.dll", StringComparison.Ordinal)).ToArray(); } projectParts.Add(AddProjectPart(assembly, riderAssembly, coreReferences, additionalAssets)); } } var stringBuilder = new StringBuilder(); SyncSolution(stringBuilder, projectParts, types); stringBuilder.Clear(); foreach (var projectPart in projectParts) { SyncProject(stringBuilder, projectPart, assemblyUsage, types); stringBuilder.Clear(); } } private static ProjectPart AddProjectPart(string assemblyName, Assembly riderAssembly, string[] coreReferences, List additionalAssets) { Assembly assembly = null; if (riderAssembly != null) { // We want to add those references, so that Rider would detect Unity path and version and provide rich features for shader files // Note that output path will be Library/ScriptAssemblies assembly = new Assembly(assemblyName, riderAssembly.outputPath, Array.Empty(), new []{"UNITY_EDITOR"}, Array.Empty(), coreReferences, riderAssembly.flags); } return new ProjectPart(assemblyName, assembly, additionalAssets); } private Dictionary> GetAdditionalAssets() { var assemblyDllNames = new FilePathTrie(); var interestingAssets = new List(); foreach (var assetPath in m_AssemblyNameProvider.GetAllAssetPaths()) { if (m_AssemblyNameProvider.IsInternalizedPackagePath(assetPath)) continue; // Find all the .asmdef and .asmref files. Then get the assembly for a file in the same folder. Anything in that // folder or below will be in the same assembly (unless there's another nested .asmdef, obvs) if (assetPath.EndsWith(".asmdef", StringComparison.OrdinalIgnoreCase) || assetPath.EndsWith(".asmref", StringComparison.OrdinalIgnoreCase)) { // This call is very expensive when working with a very large project (e.g. called for 50,000+ assets), hence // the approach of working with assembly definition root folders. We don't need a real script file to get the // assembly DLL name var assemblyDllName = m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(assetPath + ".cs"); assemblyDllNames.Insert(Path.GetDirectoryName(assetPath), assemblyDllName); } interestingAssets.Add(assetPath); } const string fallbackAssemblyDllName = "Assembly-CSharp.dll"; var assetsByAssemblyDll = new Dictionary>(); foreach (var asset in interestingAssets) { // TODO: Can we remove folders from generated projects? // Why do we add them? We get an asset for every folder, including intermediate folders. We add folders that // contain assets that we don't add to project files, so they appear empty. Adding them to the project file does // not give us anything special - they appear as a folder in the Solution Explorer, so we can right click and // add a file, but we could also "Show All Files" and do the same. Equally, Rider defaults to the Unity Explorer // view, which shows all files and folders by default. // We gain nothing by adding folders, and for very large projects, it can be very expensive to discover what // project they should be added to, since most paths will be _above_ asmdef files, or inside Assets (which // requires the full expensive check due to Editor, Resources, etc.) // (E.g. an example large project with 45,600 assets, 5,000 are folders and only 2,500 are useful assets) if (AssetDatabase.IsValidFolder(asset)) { // var assemblyDllName = assemblyDllNames.FindClosestMatch(asset); // if (string.IsNullOrEmpty(assemblyDllName)) // { // // Can't find it in trie (Assembly-CSharp and related projects don't have .asmdef files) // assemblyDllName = m_AssemblyNameProvider.GetAssemblyNameFromScriptPath($"{asset}/asset.cs"); // } // if (string.IsNullOrEmpty(assemblyDllName)) // assemblyDllName = fallbackAssemblyDllName; // // if (!stringBuilders.TryGetValue(assemblyDllName, out var projectBuilder)) // { // projectBuilder = new StringBuilder(); // stringBuilders[assemblyDllName] = projectBuilder; //} // // projectBuilder.Append(" ") // .AppendLine(); } else { if (!asset.EndsWith(".cs", StringComparison.OrdinalIgnoreCase) && IsSupportedExtension(Path.GetExtension(asset))) { var assemblyDllName = assemblyDllNames.FindClosestMatch(asset); if (string.IsNullOrEmpty(assemblyDllName)) { // Can't find it in trie (Assembly-CSharp and related projects don't have .asmdef files) assemblyDllName = m_AssemblyNameProvider.GetAssemblyNameFromScriptPath($"{asset}.cs"); } if (string.IsNullOrEmpty(assemblyDllName)) assemblyDllName = fallbackAssemblyDllName; if (!assetsByAssemblyDll.TryGetValue(assemblyDllName, out var assets)) { assets = new List(); assetsByAssemblyDll[assemblyDllName] = assets; } assets.Add(m_FileIOProvider.EscapedRelativePathFor(asset, ProjectDirectoryWithSlash)); } } } var assetsByAssemblyName = new Dictionary>(assetsByAssemblyDll.Count); foreach (var entry in assetsByAssemblyDll) { var assemblyName = FileSystemUtil.FileNameWithoutExtension(entry.Key); assetsByAssemblyName[assemblyName] = entry.Value; } return assetsByAssemblyName; } private void SyncProject(StringBuilder stringBuilder, ProjectPart island, AssemblyUsage assemblyUsage, Type[] types) { SyncProjectFileIfNotChanged( ProjectFile(island), ProjectText(stringBuilder, island, assemblyUsage), types); } private void SyncProjectFileIfNotChanged(string path, string newContents, Type[] types) { if (Path.GetExtension(path) == ".csproj") { newContents = OnGeneratedCSProject(path, newContents, types); } SyncFileIfNotChanged(path, newContents); } private void SyncSolutionFileIfNotChanged(string path, string newContents, Type[] types) { newContents = OnGeneratedSlnSolution(path, newContents, types); SyncFileIfNotChanged(path, newContents); } private static void OnGeneratedCSProjectFiles(Type[] types) { foreach (var type in types) { var method = type.GetMethod("OnGeneratedCSProjectFiles", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); if (method == null) { continue; } Debug.LogWarning("OnGeneratedCSProjectFiles is not supported."); // RIDER-51958 //method.Invoke(null, args); } } public static Type[] GetAssetPostprocessorTypes() { return TypeCache.GetTypesDerivedFrom().ToArray(); // doesn't find types from EditorPlugin, which is fine } private static bool OnPreGeneratingCSProjectFiles(Type[] types) { var result = false; foreach (var type in types) { var args = new object[0]; var method = type.GetMethod("OnPreGeneratingCSProjectFiles", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); if (method == null) { continue; } var returnValue = method.Invoke(null, args); if (method.ReturnType == typeof(bool)) { result |= (bool)returnValue; } } return result; } private static string OnGeneratedCSProject(string path, string content, Type[] types) { foreach (var type in types) { var args = new[] { path, content }; var method = type.GetMethod("OnGeneratedCSProject", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); if (method == null) { continue; } var returnValue = method.Invoke(null, args); if (method.ReturnType == typeof(string)) { content = (string)returnValue; } } return content; } private static string OnGeneratedSlnSolution(string path, string content, Type[] types) { foreach (var type in types) { var args = new[] { path, content }; var method = type.GetMethod("OnGeneratedSlnSolution", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static); if (method == null) { continue; } var returnValue = method.Invoke(null, args); if (method.ReturnType == typeof(string)) { content = (string)returnValue; } } return content; } private void SyncFileIfNotChanged(string path, string newContents) { if (HasChanged(path, newContents)) m_FileIOProvider.WriteAllText(path, newContents); } private static char[] _buffer = null; private bool HasChanged(string path, string newContents) { try { if (!m_FileIOProvider.Exists(path)) return true; const int bufferSize = 100 * 1024; // 100kb - big enough to read most project files in a single read if (_buffer == null) _buffer = new char[bufferSize]; using (var reader = m_FileIOProvider.GetReader(path)) { int read, offset = 0; do { read = reader.ReadBlock(_buffer, 0, _buffer.Length); for (var i = 0; i < read; i++) { if (_buffer[i] != newContents[offset + i]) return true; } offset += read; } while (read > 0); var isSame = offset == newContents.Length; return !isSame; } } catch (Exception exception) { Debug.LogException(exception); return true; } } private string ProjectText(StringBuilder projectBuilder, ProjectPart assembly, AssemblyUsage assemblyUsage) { var responseFilesData = assembly.GetResponseFileData(m_AssemblyNameProvider, ProjectDirectory); ProjectHeader(projectBuilder, assembly, responseFilesData); projectBuilder.AppendLine(" "); foreach (var file in assembly.SourceFiles) { var fullFile = m_FileIOProvider.EscapedRelativePathFor(file, ProjectDirectory); projectBuilder.Append(" "); } foreach (var additionalAsset in (IEnumerable)assembly.AdditionalAssets ?? Array.Empty()) projectBuilder.Append(" "); var binaryReferences = new HashSet(assembly.CompiledAssemblyReferences); foreach (var responseFileData in responseFilesData) binaryReferences.UnionWith(responseFileData.FullPathReferences); foreach (var assemblyReference in assembly.AssemblyReferences) { if (assemblyUsage.IsPrecompiledAssembly(assemblyReference)) binaryReferences.Add(assemblyReference.outputPath); } foreach (var reference in binaryReferences) { var escapedFullPath = GetNormalisedAssemblyPath(reference); var assemblyName = GetAssemblyNameFromPath(reference); projectBuilder .Append(" ") .Append(" ").Append(escapedFullPath).AppendLine("") .AppendLine(" "); } if (0 < assembly.AssemblyReferences.Length) { projectBuilder .AppendLine(" ") .AppendLine(" "); foreach (var reference in assembly.AssemblyReferences) { if (assemblyUsage.IsProjectAssembly(reference)) { var name = m_AssemblyNameProvider.GetProjectName(reference.name, reference.defines); projectBuilder .Append(" ") .Append(" {").Append(ProjectGuid(name)).AppendLine("}") .Append(" ").Append(name).AppendLine("") .AppendLine(" "); } } } projectBuilder .AppendLine(" ") .AppendLine(" ") .AppendLine( " ") .AppendLine(""); return projectBuilder.ToString(); } private string ProjectFile(ProjectPart projectPart) { return Path.Combine(ProjectDirectory, $"{m_AssemblyNameProvider.GetProjectName(projectPart.Name, projectPart.Defines)}.csproj"); } public string SolutionFile() { return Path.Combine(ProjectDirectory, $"Unity.sln"); } private void ProjectHeader(StringBuilder stringBuilder, ProjectPart assembly, List responseFilesData) { var responseFilesDataArgs = GetOtherArgumentsFromResponseFilesData(responseFilesData); stringBuilder .AppendLine("") .AppendLine( "") .AppendLine(" ") .Append(" ").Append(GetLangVersion(responseFilesDataArgs["langversion"], assembly)).AppendLine("") .AppendLine( " <_TargetFrameworkDirectories>non_empty_path_generated_by_unity.rider.package") .AppendLine( " <_FullFrameworkReferenceAssemblyPaths>non_empty_path_generated_by_unity.rider.package") .AppendLine(" true"); var rulesetPaths = GetRoslynAnalyzerRulesetPaths(assembly, responseFilesDataArgs); foreach (var path in rulesetPaths) stringBuilder.Append(" ").Append(path).AppendLine(""); stringBuilder .AppendLine(" ") .AppendLine(" ") .AppendLine(" Debug") .AppendLine(" AnyCPU") .AppendLine(" 10.0.20506") .AppendLine(" 2.0") .Append(" ").Append(assembly.RootNamespace).AppendLine("") .Append(" {").Append(ProjectGuid(m_AssemblyNameProvider.GetProjectName(assembly.Name, assembly.Defines))).AppendLine("}") .AppendLine( " {E097FAD1-6243-4DAD-9C02-E9B9EFC3FFC1};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") .AppendLine(" Library") .AppendLine(" Properties") .Append(" ").Append(assembly.Name).AppendLine("") .AppendLine(" v4.7.1") .AppendLine(" 512") .AppendLine(" .") .AppendLine(" ") .AppendLine(" ") .AppendLine(" true") .AppendLine(" full") .AppendLine(" false") .Append(" ").Append(assembly.OutputPath).AppendLine(""); var defines = new HashSet(assembly.Defines); foreach (var responseFileData in responseFilesData) defines.UnionWith(responseFileData.Defines); stringBuilder .Append(" ").CompatibleAppendJoin(';', defines).AppendLine("") .AppendLine(" prompt"); var warningLevel = responseFilesDataArgs["warn"].Concat(responseFilesDataArgs["w"]).Distinct().FirstOrDefault(); stringBuilder .Append(" ").Append(!string.IsNullOrWhiteSpace(warningLevel) ? warningLevel : "4").AppendLine("") .Append(" ").CompatibleAppendJoin(',', GetNoWarn(responseFilesDataArgs["nowarn"].ToList())).AppendLine("") .Append(" ").Append(assembly.CompilerOptions.AllowUnsafeCode | responseFilesData.Any(x => x.Unsafe)).AppendLine(""); AppendWarningAsError(stringBuilder, responseFilesDataArgs["warnaserror"], responseFilesDataArgs["warnaserror-"], responseFilesDataArgs["warnaserror+"]); // TODO: Can we have multiple documentation files in a project file? foreach (var docFile in responseFilesDataArgs["doc"]) stringBuilder.Append(" ").Append(docFile).AppendLine(""); var nullable = responseFilesDataArgs["nullable"].FirstOrDefault(); if (!string.IsNullOrEmpty(nullable)) stringBuilder.Append(" ").Append(nullable).AppendLine(""); stringBuilder .AppendLine(" ") .AppendLine(" ") .AppendLine(" true") .AppendLine(" true") .AppendLine(" false") .AppendLine(" false") .AppendLine(" false") .AppendLine(" "); var analyzers = GetRoslynAnalyzers(assembly, responseFilesDataArgs); if (analyzers.Length > 0) { stringBuilder.AppendLine(" "); foreach (var analyzer in analyzers) stringBuilder.AppendLine($" "); stringBuilder.AppendLine(" "); } var additionalFiles = GetRoslynAdditionalFiles(assembly, responseFilesDataArgs); if (additionalFiles.Length > 0) { stringBuilder.AppendLine(" "); foreach (var additionalFile in additionalFiles) stringBuilder.AppendLine($" "); stringBuilder.AppendLine(" "); } var configFile = GetGlobalAnalyzerConfigFile(assembly); if (!string.IsNullOrEmpty(configFile)) { stringBuilder .AppendLine(" ") .Append(" ") .AppendLine(" "); } } private static string GetGlobalAnalyzerConfigFile(ProjectPart assembly) { var configFile = string.Empty; #if UNITY_2021_3 // https://github.com/JetBrains/resharper-unity/issues/2401 var type = assembly.CompilerOptions.GetType(); var propertyInfo = type.GetProperty("AnalyzerConfigPath"); if (propertyInfo != null && propertyInfo.GetValue(assembly.CompilerOptions) is string value) { configFile = value; } #elif UNITY_2022_2_OR_NEWER configFile = assembly.CompilerOptions.AnalyzerConfigPath; // https://docs.unity3d.com/2021.3/Documentation/ScriptReference/Compilation.ScriptCompilerOptions.AnalyzerConfigPath.html #endif return configFile; } private static string[] GetRoslynAdditionalFiles(ProjectPart assembly, ILookup otherResponseFilesData) { var additionalFilePathsFromCompilationPipeline = Array.Empty(); #if UNITY_2021_3 // https://github.com/JetBrains/resharper-unity/issues/2401 var type = assembly.CompilerOptions.GetType(); var propertyInfo = type.GetProperty("RoslynAdditionalFilePaths"); if (propertyInfo != null && propertyInfo.GetValue(assembly.CompilerOptions) is string[] value) { additionalFilePathsFromCompilationPipeline = value; } #elif UNITY_2022_2_OR_NEWER // https://docs.unity3d.com/2021.3/Documentation/ScriptReference/Compilation.ScriptCompilerOptions.RoslynAdditionalFilePaths.html additionalFilePathsFromCompilationPipeline = assembly.CompilerOptions.RoslynAdditionalFilePaths; #endif return otherResponseFilesData["additionalfile"] .SelectMany(x=>x.Split(';')) .Concat(additionalFilePathsFromCompilationPipeline) .Distinct().ToArray(); } string[] GetRoslynAnalyzers(ProjectPart assembly, ILookup otherResponseFilesData) { #if UNITY_2020_2_OR_NEWER return otherResponseFilesData["analyzer"].Concat(otherResponseFilesData["a"]) .SelectMany(x=>x.Split(';')) #if !ROSLYN_ANALYZER_FIX .Concat(m_AssemblyNameProvider.GetRoslynAnalyzerPaths()) #else .Concat(assembly.CompilerOptions.RoslynAnalyzerDllPaths) #endif .Select(GetNormalisedAssemblyPath) .Distinct() .ToArray(); #else return otherResponseFilesData["analyzer"].Concat(otherResponseFilesData["a"]) .SelectMany(x=>x.Split(';')) .Distinct() .Select(GetNormalisedAssemblyPath) .ToArray(); #endif } private IEnumerable GetRoslynAnalyzerRulesetPaths(ProjectPart assembly, ILookup otherResponseFilesData) { var paths = new HashSet(otherResponseFilesData["ruleset"]); #if UNITY_2020_2_OR_NEWER if (!string.IsNullOrEmpty(assembly.CompilerOptions.RoslynAnalyzerRulesetPath)) paths.Add(assembly.CompilerOptions.RoslynAnalyzerRulesetPath); #endif return paths.Select(GetNormalisedAssemblyPath); } private static void AppendWarningAsError(StringBuilder stringBuilder, IEnumerable args, IEnumerable argsMinus, IEnumerable argsPlus) { var treatWarningsAsErrors = false; var warningIds = new List(); var notWarningIds = new List(argsMinus); foreach (var s in args) { if (s == "+" || s == "") treatWarningsAsErrors = true; else if (s == "-") treatWarningsAsErrors = false; else warningIds.Add(s); } warningIds.AddRange(argsPlus); stringBuilder.Append(" ").Append(treatWarningsAsErrors) .AppendLine(""); if (warningIds.Count > 0) stringBuilder.Append(" ").CompatibleAppendJoin(';', warningIds).AppendLine(""); if (notWarningIds.Count > 0) stringBuilder.Append(" ").CompatibleAppendJoin(';', notWarningIds) .AppendLine(""); } private void SyncSolution(StringBuilder stringBuilder, List islands, Type[] types) { SyncSolutionFileIfNotChanged(SolutionFile(), SolutionText(stringBuilder, islands), types); } private string SolutionText(StringBuilder stringBuilder, List islands) { stringBuilder .AppendLine() .AppendLine("Microsoft Visual Studio Solution File, Format Version 11.00") .AppendLine("# Visual Studio 2010"); foreach (var island in islands) { var projectName = m_AssemblyNameProvider.GetProjectName(island.Name, island.Defines); // GUID is for C# class libraries stringBuilder .Append("Project(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"") .Append(island.Name) .Append("\", \"") .Append(projectName) .Append(".csproj\", \"{") .Append(ProjectGuid(projectName)) .AppendLine("}\"") .AppendLine("EndProject"); } stringBuilder.AppendLine("Global") .AppendLine("\tGlobalSection(SolutionConfigurationPlatforms) = preSolution") .AppendLine("\t\tDebug|Any CPU = Debug|Any CPU") .AppendLine("\tEndGlobalSection") .AppendLine("\tGlobalSection(ProjectConfigurationPlatforms) = postSolution"); foreach (var island in islands) { var projectGuid = ProjectGuid(m_AssemblyNameProvider.GetProjectName(island.Name, island.Defines)); stringBuilder .Append("\t\t{").Append(projectGuid).AppendLine("}.Debug|Any CPU.ActiveCfg = Debug|Any CPU") .Append("\t\t{").Append(projectGuid).AppendLine("}.Debug|Any CPU.Build.0 = Debug|Any CPU"); } stringBuilder.AppendLine("\tEndGlobalSection") .AppendLine("\tGlobalSection(SolutionProperties) = preSolution") .AppendLine("\t\tHideSolutionNode = FALSE") .AppendLine("\tEndGlobalSection") .AppendLine("EndGlobal"); return stringBuilder.ToString(); } private static ILookup GetOtherArgumentsFromResponseFilesData(List responseFilesData) { var paths = responseFilesData.SelectMany(x => { return x.OtherArguments .Where(a => a.StartsWith("/", StringComparison.Ordinal) || a.StartsWith("-", StringComparison.Ordinal)) .Select(b => { var index = b.IndexOf(":", StringComparison.Ordinal); if (index > 0 && b.Length > index) { var key = b.Substring(1, index - 1); return new KeyValuePair(key, b.Substring(index + 1)); } const string warnaserror = "warnaserror"; if (b.Substring(1).StartsWith(warnaserror, StringComparison.Ordinal)) { return new KeyValuePair(warnaserror, b.Substring(warnaserror.Length + 1)); } const string nullable = "nullable"; if (b.Substring(1).StartsWith(nullable, StringComparison.Ordinal)) { var res = b.Substring(nullable.Length + 1); if (string.IsNullOrWhiteSpace(res) || res.Equals("+")) res = "enable"; else if (res.Equals("-")) res = "disable"; return new KeyValuePair(nullable, res); } return default; }); }) .Distinct() .ToLookup(o => o.Key, pair => pair.Value); return paths; } private string GetLangVersion(IEnumerable langVersionList, ProjectPart assembly) { var langVersion = langVersionList.FirstOrDefault(); if (!string.IsNullOrWhiteSpace(langVersion)) return langVersion; #if UNITY_2020_2_OR_NEWER return assembly.CompilerOptions.LanguageVersion; #else return "latest"; #endif } public static IEnumerable GetNoWarn(List codes) { #if UNITY_2019_4 || UNITY_2020_1 // RIDER-77206 Unity 2020.1.3 'PlayerSettings' does not contain a definition for 'suppressCommonWarnings' var type = typeof(PlayerSettings); var propertyInfo = type.GetProperty("suppressCommonWarnings"); if (propertyInfo != null && propertyInfo.GetValue(null) is bool && (bool)propertyInfo.GetValue(null)) { codes.AddRange(new[] {"0169", "0649"}); } #elif UNITY_2020_2_OR_NEWER if (PlayerSettings.suppressCommonWarnings) codes.AddRange(new[] {"0169", "0649"}); #endif return codes.Distinct(); } private string ProjectGuid(string name) { if (!m_ProjectGuids.TryGetValue(name, out var guid)) { guid = m_GUIDGenerator.ProjectGuid(m_ProjectName + name); m_ProjectGuids.Add(name, guid); } return guid; } private string GetNormalisedAssemblyPath(string path) { if (!m_NormalisedPaths.TryGetValue(path, out var normalisedPath)) { normalisedPath = Path.IsPathRooted(path) ? path : Path.GetFullPath(path); normalisedPath = SecurityElement.Escape(normalisedPath).NormalizePath(); m_NormalisedPaths.Add(path, normalisedPath); } return normalisedPath; } private string GetAssemblyNameFromPath(string path) { if (!m_AssemblyNames.TryGetValue(path, out var name)) { name = FileSystemUtil.FileNameWithoutExtension(path); m_AssemblyNames.Add(path, name); } return name; } } internal class FilePathTrie { private static readonly char[] Separators = { '\\', '/' }; private readonly TrieNode m_Root = new TrieNode(); private class TrieNode { public Dictionary Children; public TData Data; } public void Insert(string filePath, TData data) { var parts = filePath.Split(Separators); var node = m_Root; foreach (var part in parts) { if (node.Children == null) node.Children = new Dictionary(StringComparer.OrdinalIgnoreCase); // ReSharper disable once CanSimplifyDictionaryLookupWithTryAdd if (!node.Children.ContainsKey(part)) node.Children[part] = new TrieNode(); node = node.Children[part]; } node.Data = data; } public TData FindClosestMatch(string filePath) { var parts = filePath.Split(Separators); var node = m_Root; foreach (var part in parts) { if (node.Children != null && node.Children.TryGetValue(part, out var next)) node = next; else break; } return node.Data; } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/ProjectGeneration.cs.meta ================================================ fileFormatVersion: 2 guid: 7078f19173ceac84fb9e29b9f6175201 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/ProjectGenerationFlag.cs ================================================ using System; namespace Packages.Rider.Editor.ProjectGeneration { [Flags] enum ProjectGenerationFlag { None = 0, Embedded = 1, Local = 2, Registry = 4, Git = 8, BuiltIn = 16, Unknown = 32, PlayerAssemblies = 64, LocalTarBall = 128, } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/ProjectGenerationFlag.cs.meta ================================================ fileFormatVersion: 2 guid: 78af99d0156944f293b020b49a6830c2 timeCreated: 1580820569 ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/ProjectPart.cs ================================================ using System; using System.Collections.Generic; using UnityEditor.Compilation; using UnityEngine; namespace Packages.Rider.Editor.ProjectGeneration { internal class ProjectPart { public string Name { get; } public string OutputPath { get; } public Assembly Assembly { get; } public List AdditionalAssets { get; } public string[] SourceFiles { get; } public string RootNamespace { get; } public Assembly[] AssemblyReferences { get; } public string[] CompiledAssemblyReferences { get; } public string[] Defines { get; } public ScriptCompilerOptions CompilerOptions { get; } public ProjectPart(string name, Assembly assembly, List additionalAssets) { Name = name; Assembly = assembly; AdditionalAssets = additionalAssets; OutputPath = assembly != null ? assembly.outputPath : "Temp/Bin/Debug"; SourceFiles = assembly != null ? assembly.sourceFiles : Array.Empty(); #if UNITY_2020_2_OR_NEWER RootNamespace = assembly != null ? assembly.rootNamespace : string.Empty; #else RootNamespace = UnityEditor.EditorSettings.projectGenerationRootNamespace; #endif AssemblyReferences = assembly != null ? assembly.assemblyReferences : Array.Empty(); CompiledAssemblyReferences = assembly != null ? assembly.compiledAssemblyReferences : Array.Empty(); Defines = assembly != null ? assembly.defines : Array.Empty(); CompilerOptions = assembly != null ? assembly.compilerOptions : new ScriptCompilerOptions(); } public List GetResponseFileData(IAssemblyNameProvider assemblyNameProvider, string projectDirectory) { if (Assembly == null) return new List(); var data = new List(); foreach (var responseFile in Assembly.compilerOptions.ResponseFiles) { var responseFileData = assemblyNameProvider.ParseResponseFile(responseFile, projectDirectory, Assembly.compilerOptions.ApiCompatibilityLevel); foreach (var error in responseFileData.Errors) Debug.Log($"{responseFile} Parse Error : {error}"); data.Add(responseFileData); } return data; } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/ProjectPart.cs.meta ================================================ fileFormatVersion: 2 guid: 0691c2fe40564a64b3a9b7372c5eca2a timeCreated: 1604050230 ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/SolutionGuidGenerator.cs ================================================ using System; using System.Security.Cryptography; using System.Text; namespace Packages.Rider.Editor.ProjectGeneration { internal static class SolutionGuidGenerator { public static string GuidForProject(string projectName) { return ComputeGuidHashFor(projectName + "salt"); } private static string ComputeGuidHashFor(string input) { using (var md5 = MD5.Create()) { var hash = md5.ComputeHash(Encoding.Default.GetBytes(input)); return new Guid(hash).ToString(); } } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration/SolutionGuidGenerator.cs.meta ================================================ fileFormatVersion: 2 guid: 3e4e7fdc19414089a0fb43e43b1bdae1 timeCreated: 1580717740 ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/ProjectGeneration.meta ================================================ fileFormatVersion: 2 guid: 313cbe17019f1934397f91069831062c folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Properties/AssemblyInfo.cs ================================================ using System.Reflection; using System.Runtime.CompilerServices; [assembly: AssemblyTitle("Unity.Rider.Editor")] [assembly: InternalsVisibleTo("Unity.Rider.EditorTests")] [assembly: InternalsVisibleTo("Unity.PackageValidationSuite.Editor")] [assembly: InternalsVisibleTo("Assembly-CSharp-Editor")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] [assembly: AssemblyVersion("3.0.7")] ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Properties/AssemblyInfo.cs.meta ================================================ fileFormatVersion: 2 guid: 8472c6472e6d4301873871a9e8fcf952 timeCreated: 1580716711 ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Properties.meta ================================================ fileFormatVersion: 2 guid: 354fde95fe0643b09509e5fcee350b33 timeCreated: 1580716670 ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/RiderInitializer.cs ================================================ using System; using System.IO; using System.Linq; using System.Reflection; using Rider.Editor.Util; using UnityEngine; using Debug = UnityEngine.Debug; namespace Packages.Rider.Editor { internal class RiderInitializer { public void Initialize(string editorPath) { var assembly = EditorPluginInterop.EditorPluginAssembly; if (EditorPluginInterop.EditorPluginIsLoadedFromAssets(assembly)) { Debug.LogError($"Please delete {assembly.Location}. Unity 2019.2+ loads it directly from Rider installation. To disable this, open Rider's settings, search and uncheck 'Automatically install and update Rider's Unity editor plugin'."); return; } if (assembly != null) // already loaded RIDER-92419 { return; } // for debugging rider editor plugin if (RiderPathUtil.IsRiderDevEditor(editorPath)) { LoadEditorPluginForDevEditor(editorPath); } else { var relPath = "../../plugins/rider-unity/EditorPlugin"; if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX) relPath = "Contents/plugins/rider-unity/EditorPlugin"; var baseDir = Path.Combine(editorPath, relPath); var dllFile = new FileInfo(Path.Combine(baseDir, $"{EditorPluginInterop.EditorPluginAssemblyName}.dll")); if (!dllFile.Exists) dllFile = new FileInfo(Path.Combine(baseDir, $"{EditorPluginInterop.EditorPluginAssemblyNameFallback}.dll")); if (dllFile.Exists) { var bytes = File.ReadAllBytes(dllFile.FullName); assembly = AppDomain.CurrentDomain.Load(bytes); // doesn't lock assembly on disk if (PluginSettings.SelectedLoggingLevel >= LoggingLevel.TRACE) Debug.Log($"Rider EditorPlugin loaded from {dllFile.FullName}"); EditorPluginInterop.InitEntryPoint(assembly); } else { Debug.Log($"Unable to find Rider EditorPlugin {dllFile.FullName} for Unity "); } } } private static void LoadEditorPluginForDevEditor(string editorPath) { var file = new FileInfo(editorPath); if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX) file = new FileInfo(Path.Combine(editorPath, "rider-dev.bat")); if (!file.Exists) { Debug.Log($"Unable to determine path to EditorPlugin from {file}"); return; } var dllPath = File.ReadLines(file.FullName).FirstOrDefault(); if (dllPath == null) { Debug.Log($"Unable to determine path to EditorPlugin from {file}"); return; } var dllFile = new FileInfo(dllPath); if (!dllFile.Exists) { Debug.Log($"Unable to find Rider EditorPlugin {dllPath} for Unity "); return; } var assembly = AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName(dllFile.FullName)); if (PluginSettings.SelectedLoggingLevel >= LoggingLevel.TRACE) Debug.Log($"Rider EditorPlugin loaded from {dllFile.FullName}"); EditorPluginInterop.InitEntryPoint(assembly); } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/RiderInitializer.cs.meta ================================================ fileFormatVersion: 2 guid: f5a0cc9645f0e2d4fb816156dcf3f4dd MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/RiderScriptEditor.cs ================================================ using System; using System.Collections.Generic; using System.IO; using System.Linq; using JetBrains.Annotations; using JetBrains.Rider.PathLocator; using Packages.Rider.Editor.ProjectGeneration; using Packages.Rider.Editor.Util; using Unity.CodeEditor; using UnityEditor; using UnityEngine; using Debug = UnityEngine.Debug; using OperatingSystemFamily = UnityEngine.OperatingSystemFamily; namespace Packages.Rider.Editor { [InitializeOnLoad] internal class RiderScriptEditor : IExternalCodeEditor { IDiscovery m_Discoverability; static IGenerator m_ProjectGeneration; RiderInitializer m_Initiliazer = new RiderInitializer(); static RiderScriptEditor m_RiderScriptEditor; static RiderScriptEditor() { try { // todo: make ProjectGeneration lazy var projectGeneration = new ProjectGeneration.ProjectGeneration(); m_RiderScriptEditor = new RiderScriptEditor(new Discovery(), projectGeneration); // preserve the order here, otherwise on startup, project generation Sync would happen multiple times CodeEditor.Register(m_RiderScriptEditor); InitializeInternal(CurrentEditor); // end of "preserve the order here" } catch (Exception e) { Debug.LogException(e); } } private static void ShowWarningOnUnexpectedScriptEditor(string path) { // Show warning, when Unity was started from Rider, but external editor is different https://github.com/JetBrains/resharper-unity/issues/1127 try { var args = Environment.GetCommandLineArgs(); var commandlineParser = new CommandLineParser(args); if (commandlineParser.Options.ContainsKey("-riderPath")) { var originRiderPath = commandlineParser.Options["-riderPath"]; var originRealPath = GetEditorRealPath(originRiderPath); var originVersion = Discovery.RiderPathLocator.GetBuildNumber(originRealPath); var version = Discovery.RiderPathLocator.GetBuildNumber(path); if (originVersion != null && originVersion != version) { Debug.LogWarning("Unity was started by a version of Rider that is not the current default external editor. Advanced integration features cannot be enabled."); Debug.Log($"Unity was started by Rider {originVersion}, but external editor is set to: {path}"); } } } catch (Exception e) { Debug.LogException(e); } } internal static string GetEditorRealPath(string path) { if (string.IsNullOrEmpty(path)) return path; if (!FileSystemUtil.EditorPathExists(path)) return path; if (SystemInfo.operatingSystemFamily != OperatingSystemFamily.Windows) { var realPath = FileSystemUtil.GetFinalPathName(path); // case of snap installation if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.Linux) { if (new FileInfo(path).Name.ToLowerInvariant() == "rider" && new FileInfo(realPath).Name.ToLowerInvariant() == "snap") { var snapInstallPath = "/snap/rider/current/bin/rider.sh"; if (new FileInfo(snapInstallPath).Exists) return snapInstallPath; } } // in case of symlink return realPath; } return new FileInfo(path).FullName; } public RiderScriptEditor(IDiscovery discovery, IGenerator projectGeneration) { m_Discoverability = discovery; m_ProjectGeneration = projectGeneration; } public void OnGUI() { GUILayout.BeginHorizontal(); var style = GUI.skin.label; var text = "Customize handled extensions in"; EditorGUILayout.LabelField(text, style, GUILayout.Width(style.CalcSize(new GUIContent(text)).x)); if (PluginSettings.LinkButton("Project Settings | Editor | Additional extensions to include")) { SettingsService.OpenProjectSettings("Project/Editor"); // how do I focus "Additional extensions to include"? } GUILayout.EndHorizontal(); EditorGUILayout.LabelField("Generate .csproj files for:"); EditorGUI.indentLevel++; SettingsButton(ProjectGenerationFlag.Embedded, "Embedded packages", ""); SettingsButton(ProjectGenerationFlag.Local, "Local packages", ""); SettingsButton(ProjectGenerationFlag.Registry, "Registry packages", ""); SettingsButton(ProjectGenerationFlag.Git, "Git packages", ""); SettingsButton(ProjectGenerationFlag.BuiltIn, "Built-in packages", ""); #if UNITY_2019_3_OR_NEWER SettingsButton(ProjectGenerationFlag.LocalTarBall, "Local tarball", ""); #endif SettingsButton(ProjectGenerationFlag.Unknown, "Packages from unknown sources", ""); SettingsButton(ProjectGenerationFlag.PlayerAssemblies, "Player projects", "For each player project generate an additional csproj with the name 'project-player.csproj'"); RegenerateProjectFiles(); EditorGUI.indentLevel--; } void RegenerateProjectFiles() { var rect = EditorGUI.IndentedRect(EditorGUILayout.GetControlRect(new GUILayoutOption[] {})); rect.width = 252; if (GUI.Button(rect, "Regenerate project files")) { m_ProjectGeneration.Sync(); } } void SettingsButton(ProjectGenerationFlag preference, string guiMessage, string toolTip) { var prevValue = m_ProjectGeneration.AssemblyNameProvider.ProjectGenerationFlag.HasFlag(preference); var newValue = EditorGUILayout.Toggle(new GUIContent(guiMessage, toolTip), prevValue); if (newValue != prevValue) { m_ProjectGeneration.AssemblyNameProvider.ToggleProjectGeneration(preference); } } public void SyncIfNeeded(string[] addedFiles, string[] deletedFiles, string[] movedFiles, string[] movedFromFiles, string[] importedFiles) { m_ProjectGeneration.SyncIfNeeded(addedFiles.Union(deletedFiles).Union(movedFiles).Union(movedFromFiles), importedFiles); } public void SyncAll() { m_ProjectGeneration.Sync(); } [UsedImplicitly] public static void SyncSolution() // generate-the-sln-file-via-script-or-command-line { m_ProjectGeneration.Sync(); } [UsedImplicitly] // called from Rider EditorPlugin with reflection public static void SyncIfNeeded(bool checkProjectFiles) { AssetDatabase.Refresh(); m_ProjectGeneration.SyncIfNeeded(new string[] { }, new string[] { }, checkProjectFiles); } [UsedImplicitly] public static void SyncSolutionAndOpenExternalEditor() { m_ProjectGeneration.Sync(); CodeEditor.CurrentEditor.OpenProject(); } /// /// In 2020.x is called each time ExternalEditor is changed /// In 2021.x+ is called each time ExternalEditor is changed and also on each appdomain reload /// /// public void Initialize(string editorInstallationPath) { var prevEditorVersion = RiderScriptEditorData.instance.prevEditorBuildNumber.ToVersion(); RiderScriptEditorData.instance.Invalidate(editorInstallationPath, true); // previous editor did not have EditorPlugin // just load the EditorPlugin if (EditorPluginInterop.EditorPluginAssembly == null) { InitializeInternal(editorInstallationPath); return; } // previous editor was Rider with a different version // need to load new Editor plugin if (prevEditorVersion != null && prevEditorVersion != RiderScriptEditorData.instance.editorBuildNumber.ToVersion()) // in Unity 2019.3 any change in preference causes `Initialize` call { #if UNITY_2019_3_OR_NEWER EditorUtility.RequestScriptReload(); // EditorPlugin would get loaded #else UnityEditorInternal.InternalEditorUtility.RequestScriptReload(); #endif } } private static void InitializeInternal(string currentEditorPath) { var path = GetEditorRealPath(currentEditorPath); if (IsRiderOrFleetInstallation(path)) { var installations = new HashSet(); if (RiderScriptEditorData.instance.installations != null) { foreach (var info in RiderScriptEditorData.instance.installations) { installations.Add(info); } } if (!RiderScriptEditorData.instance.initializedOnce || !FileSystemUtil.EditorPathExists(path)) { foreach (var item in Discovery.RiderPathLocator.GetAllRiderPaths()) { installations.Add(item); } // is likely outdated if (installations.All(a => GetEditorRealPath(a.Path) != path)) { if (Discovery.RiderPathLocator.GetIsToolbox(path)) // is toolbox 1.x - update { var toolboxInstallations = installations.Where(a => a.IsToolbox).ToArray(); if (toolboxInstallations.Any()) { var newEditor = toolboxInstallations.OrderBy(a => a.BuildNumber).Last().Path; CodeEditor.SetExternalScriptEditor(newEditor); path = newEditor; } else if (installations.Any()) { var newEditor = installations.OrderBy(a => a.BuildNumber).Last().Path; CodeEditor.SetExternalScriptEditor(newEditor); path = newEditor; } } else if (installations.Any()) // is non toolbox 1.x { if (!FileSystemUtil.EditorPathExists(path)) // previously used rider was removed { var newEditor = installations.OrderBy(a => a.BuildNumber).Last().Path; CodeEditor.SetExternalScriptEditor(newEditor); path = newEditor; } else // notify { var newEditorName = installations.OrderBy(a => a.BuildNumber).Last().Presentation; Debug.LogWarning($"Consider updating External Editor in Unity to {newEditorName}."); } } } ShowWarningOnUnexpectedScriptEditor(path); RiderScriptEditorData.instance.initializedOnce = true; } if (FileSystemUtil.EditorPathExists(path) && installations.All(a => a.Path != path)) // custom location { var info = new RiderPathLocator.RiderInfo(Discovery.RiderPathLocator, path, Discovery.RiderPathLocator.GetIsToolbox(path)); installations.Add(info); } RiderScriptEditorData.instance.installations = installations.ToArray(); RiderScriptEditorData.instance.Init(); m_RiderScriptEditor.CreateSolutionIfDoesntExist(); if (RiderScriptEditorData.instance.shouldLoadEditorPlugin) { m_RiderScriptEditor.m_Initiliazer.Initialize(path); } // can't switch to non-deprecated api, because UnityEditor.Build.BuildPipelineInterfaces.processors is internal #pragma warning disable 618 EditorUserBuildSettings.activeBuildTargetChanged += () => #pragma warning restore 618 { RiderScriptEditorData.instance.hasChanges = true; }; } } public bool OpenProject(string path, int line, int column) { var projectGeneration = (ProjectGeneration.ProjectGeneration) m_ProjectGeneration; // Assets - Open C# Project passes empty path here if (path != "" && !projectGeneration.HasValidExtension(path)) { return false; } //if (!IsUnityScript(path)) //{ // m_ProjectGeneration.SyncIfNeeded(affectedFiles: new string[] { }, new string[] { }); // var fastOpenResult = EditorPluginInterop.OpenFileDllImplementation(path, line, column); // if (fastOpenResult) // return true; //} var slnFile = GetSolutionFile(path); return Discovery.RiderFileOpener.OpenFile(CurrentEditor, slnFile, path, line, column); } private string GetSolutionFile(string path) { //if (IsUnityScript(path)) //{ // return Path.Combine(GetBaseUnityDeveloperFolder(), "Projects/CSharp/Unity.CSharpProjects.gen.sln"); //} // //var solutionFile = m_ProjectGeneration.SolutionFile(); //if (File.Exists(solutionFile)) //{ // return solutionFile; //} return System.IO.Path.GetFullPath("./ET.sln"); } static bool IsUnityScript(string path) { if (UnityEditor.Unsupported.IsDeveloperBuild()) { var baseFolder = GetBaseUnityDeveloperFolder().Replace("\\", "/"); var lowerPath = path.ToLowerInvariant().Replace("\\", "/"); if (lowerPath.Contains((baseFolder + "/Runtime").ToLowerInvariant()) || lowerPath.Contains((baseFolder + "/Editor").ToLowerInvariant())) { return true; } } return false; } static string GetBaseUnityDeveloperFolder() { return Directory.GetParent(EditorApplication.applicationPath).Parent.Parent.FullName; } public bool TryGetInstallationForPath(string editorPath, out CodeEditor.Installation installation) { installation = default; if (string.IsNullOrEmpty(editorPath)) return false; if (FileSystemUtil.EditorPathExists(editorPath) && IsRiderOrFleetInstallation(editorPath)) { if (RiderScriptEditorData.instance.installations == null) // the case when other CodeEditor is set from the very Unity start { RiderScriptEditorData.instance.installations = Discovery.RiderPathLocator.GetAllRiderPaths(); } var realPath = GetEditorRealPath(editorPath); var editor = RiderScriptEditorData.instance.installations.FirstOrDefault(a => GetEditorRealPath(a.Path) == realPath); if (editor.Path != null) { installation = new CodeEditor.Installation { Name = editor.Presentation, Path = editor.Path }; return true; } installation = new CodeEditor.Installation { Name = "Rider (custom location)", Path = editorPath }; return true; } return false; } public static bool IsRiderOrFleetInstallation(string path) { if (IsAssetImportWorkerProcess()) return false; #if UNITY_2021_1_OR_NEWER if (UnityEditor.MPE.ProcessService.level == UnityEditor.MPE.ProcessLevel.Secondary) return false; #elif UNITY_2020_2_OR_NEWER if (UnityEditor.MPE.ProcessService.level == UnityEditor.MPE.ProcessLevel.Slave) return false; #elif UNITY_2020_1_OR_NEWER if (Unity.MPE.ProcessService.level == Unity.MPE.ProcessLevel.UMP_SLAVE) return false; #endif if (string.IsNullOrEmpty(path)) return false; return ExecutableStartsWith(path, "rider") || ExecutableStartsWith(path, "fleet"); } public static bool ExecutableStartsWith(string path, string input) { var fileInfo = new FileInfo(path); var filename = fileInfo.Name; return filename.StartsWith(input, StringComparison.OrdinalIgnoreCase); } private static bool IsAssetImportWorkerProcess() { #if UNITY_2020_2_OR_NEWER return UnityEditor.AssetDatabase.IsAssetImportWorkerProcess(); #elif UNITY_2019_3_OR_NEWER return UnityEditor.Experimental.AssetDatabaseExperimental.IsAssetImportWorkerProcess(); #else return false; #endif } public static string CurrentEditor // works fast, doesn't validate if executable really exists => EditorPrefs.GetString("kScriptsDefaultApp"); public CodeEditor.Installation[] Installations => m_Discoverability.PathCallback(); private void CreateSolutionIfDoesntExist() { if (!m_ProjectGeneration.HasSolutionBeenGenerated()) { m_ProjectGeneration.Sync(); } } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/RiderScriptEditor.cs.meta ================================================ fileFormatVersion: 2 guid: c4095d72f77fbb64ea39b8b3ca246622 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/RiderScriptEditorData.cs ================================================ using System; using System.Linq; using JetBrains.Rider.PathLocator; using Packages.Rider.Editor.Util; using Rider.Editor.Util; using UnityEditor; using UnityEngine; namespace Packages.Rider.Editor { internal class RiderScriptEditorData : ScriptableSingleton { // activeBuildTargetChanged has changed // making it true by default would cause multiple Sync projects on the startup [SerializeField] internal bool hasChanges; [SerializeField] internal bool shouldLoadEditorPlugin; [SerializeField] internal bool initializedOnce; [SerializeField] internal SerializableVersion editorBuildNumber; [SerializeField] internal SerializableVersion prevEditorBuildNumber; [SerializeField] internal RiderPathLocator.RiderInfo[] installations; [SerializeField] internal string[] activeScriptCompilationDefines; public void Init() { if (editorBuildNumber == null) { Invalidate(RiderScriptEditor.CurrentEditor); } } public void InvalidateSavedCompilationDefines() { activeScriptCompilationDefines = EditorUserBuildSettings.activeScriptCompilationDefines; } public bool HasChangesInCompilationDefines() { if (activeScriptCompilationDefines == null) return false; return !EditorUserBuildSettings.activeScriptCompilationDefines.SequenceEqual(activeScriptCompilationDefines); } public void Invalidate(string editorInstallationPath, bool shouldInvalidatePrevEditorBuildNumber = false) { var riderBuildNumber = Discovery.RiderPathLocator.GetBuildNumber(editorInstallationPath); editorBuildNumber = riderBuildNumber.ToSerializableVersion(); if (shouldInvalidatePrevEditorBuildNumber) prevEditorBuildNumber = editorBuildNumber; if (riderBuildNumber == null) // if we fail to parse for some reason shouldLoadEditorPlugin = true; shouldLoadEditorPlugin = riderBuildNumber >= new Version("191.7141.156"); if (RiderPathUtil.IsRiderDevEditor(editorInstallationPath)) { shouldLoadEditorPlugin = true; editorBuildNumber = new SerializableVersion(new Version("999.999.999.999")); } } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/RiderScriptEditorData.cs.meta ================================================ fileFormatVersion: 2 guid: f079e3afd077fb94fa2bda74d6409499 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/RiderScriptEditorDataPersisted.cs ================================================ using System; using UnityEditor; using UnityEngine; namespace Packages.Rider.Editor { #if UNITY_2020_1_OR_NEWER // API doesn't exist in 2019.4 [FilePath("Library/com.unity.ide.rider/PersistedState.asset", FilePathAttribute.Location.ProjectFolder)] #endif internal class RiderScriptEditorPersistedState : ScriptableSingleton { [SerializeField] private long lastWriteTicks; [SerializeField] private long manifestJsonLastWriteTicks; public DateTime? LastWrite { get => DateTime.FromBinary(lastWriteTicks); set { if (!value.HasValue) return; lastWriteTicks = value.Value.ToBinary(); Save(true); } } public DateTime? ManifestJsonLastWrite { get => DateTime.FromBinary(manifestJsonLastWriteTicks); set { if (!value.HasValue) return; manifestJsonLastWriteTicks = value.Value.ToBinary(); Save(true); } } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/RiderScriptEditorDataPersisted.cs.meta ================================================ fileFormatVersion: 2 guid: bc3a27147f944790916176e2172ad506 timeCreated: 1645618940 ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/RiderStyles.cs ================================================ using UnityEditor; using UnityEngine; namespace Packages.Rider.Editor { internal static class RiderStyles { static RiderStyles() { LinkLabelStyle = new GUIStyle(EditorStyles.linkLabel) { padding = GUI.skin.label.padding, margin = GUI.skin.label.margin }; LinkLabelStyle.padding.left = 0; LinkLabelStyle.padding.right = 0; LinkLabelStyle.margin.left = 0; LinkLabelStyle.margin.right = 0; } public static readonly GUIStyle LinkLabelStyle; } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/RiderStyles.cs.meta ================================================ fileFormatVersion: 2 guid: 56f347f155204ffebacb6ed6f4a4e65f timeCreated: 1617893911 ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/StartUpMethodExecutor.cs ================================================ using JetBrains.Annotations; using UnityEditor; namespace JetBrains.Rider.Unity.Editor { // Do not rename this class while you don't rename startup command for dotTrace profiler [UsedImplicitly] internal static class StartUpMethodExecutor { [UsedImplicitly] public static void EnterPlayMode() { EditorApplication.isPlaying = true; } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/StartUpMethodExecutor.cs.meta ================================================ fileFormatVersion: 2 guid: e7c145f2cd3c411ca7f50ebc5f5a6d24 timeCreated: 1670946079 ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/UnitTesting/CallbackData.cs ================================================ #if TEST_FRAMEWORK using System; using System.Collections.Generic; using JetBrains.Annotations; using UnityEditor; namespace Packages.Rider.Editor.UnitTesting { internal class CallbackData : ScriptableSingleton { /// /// identifies that tests were started from Rider /// public bool isRider; [UsedImplicitly] // Is used by Rider Unity plugin by reflection public static event EventHandler Changed = (sender, args) => { }; internal void RaiseChangedEvent() { Changed(null, EventArgs.Empty); } [UsedImplicitly] // Is used by Rider Unity plugin by reflection public List events = new List(); [UsedImplicitly] // Is used by Rider Unity plugin by reflection public void Clear() { events.Clear(); } } } #endif ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/UnitTesting/CallbackData.cs.meta ================================================ fileFormatVersion: 2 guid: 010246a07de7cb34185a2a7b1c1fad59 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/UnitTesting/CallbackInitializer.cs ================================================ #if TEST_FRAMEWORK using UnityEditor; using UnityEditor.TestTools.TestRunner.Api; using UnityEngine; namespace Packages.Rider.Editor.UnitTesting { [InitializeOnLoad] internal static class CallbackInitializer { static CallbackInitializer() { if (CallbackData.instance.isRider) ScriptableObject.CreateInstance().RegisterCallbacks(ScriptableObject.CreateInstance(), 0); } } } #endif ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/UnitTesting/CallbackInitializer.cs.meta ================================================ fileFormatVersion: 2 guid: aa1c6b1a353ab464782fc1e7c051eb02 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/UnitTesting/RiderTestRunner.cs ================================================ using JetBrains.Annotations; using UnityEngine; #if TEST_FRAMEWORK using System.IO; using System.Linq; using System.Reflection; using UnityEditor; using UnityEditor.TestTools.TestRunner.Api; #else using System; #endif namespace Packages.Rider.Editor.UnitTesting { /// /// Is called by Rider Unity plugin via reflections /// [UsedImplicitly] public static class RiderTestRunner { #if TEST_FRAMEWORK private static readonly TestsCallback Callback = ScriptableObject.CreateInstance(); private static string _sessionGuid; #endif /// /// Is called by Rider Unity plugin via reflections /// /// /// /// /// /// /// /// /// /// /// [UsedImplicitly] public static void RunTestsWithSyncCallbacks(string sessionId, int testMode, string[] assemblyNames, string[] testNames, string[] categoryNames, string[] groupNames, int? buildTarget, string callbacksHandlerCodeBase, string callbacksHandlerTypeName, string[] callbacksHandlerDependencies) { #if !TEST_FRAMEWORK Debug.LogError("Update Test Framework package to v.1.1.8+ to run tests from Rider."); throw new NotSupportedException("Incompatible `Test Framework` package in Unity. Update to v.1.1.8+"); #else SyncTestRunEventsHandler.instance.InitRun(sessionId, callbacksHandlerCodeBase, callbacksHandlerTypeName, callbacksHandlerDependencies); RunTests(testMode, assemblyNames, testNames, categoryNames, groupNames, buildTarget); #endif } /// /// Is called by Rider Unity plugin via reflections /// /// /// /// /// /// /// [UsedImplicitly] public static void RunTests(int testMode, string[] assemblyNames, string[] testNames, string[] categoryNames, string[] groupNames, int? buildTarget) { #if !TEST_FRAMEWORK Debug.LogError("Update Test Framework package to v.1.1.8+ to run tests from Rider."); throw new NotSupportedException("Incompatible `Test Framework` package in Unity. Update to v.1.1.8+"); #else CallbackData.instance.isRider = true; var api = ScriptableObject.CreateInstance(); var settings = new ExecutionSettings(); var filter = new Filter { assemblyNames = assemblyNames, testNames = testNames, categoryNames = categoryNames, groupNames = groupNames, targetPlatform = (BuildTarget?) buildTarget }; if (testMode > 0) // for future use - test-framework would allow running both Edit and Play test at once { filter.testMode = (TestMode) testMode; } api.RetrieveTestList(filter.testMode, adaptor => { // start tests if there any, otherwise send a RunFinished signal // see RIDER-91705 if (adaptor.Children.Any(a => a.IsTestAssembly && assemblyNames.Contains(Path.GetFileNameWithoutExtension(a.Name)))) { settings.filters = new[] { filter }; _sessionGuid = api.Execute(settings); api.UnregisterCallbacks(Callback); // avoid multiple registrations api.RegisterCallbacks(Callback); // receive information about when the test suite and individual tests starts and stops. } else { CallbackData.instance.isRider = false; CallbackData.instance.events.Add( new TestEvent(EventType.RunFinished, "", "", "", 0, NUnit.Framework.Interfaces.TestStatus.Inconclusive, "")); CallbackData.instance.RaiseChangedEvent(); } }); #endif } [UsedImplicitly] internal static void CancelTestRun() { #if !TEST_FRAMEWORK Debug.LogError("Update Test Framework package to v.1.1.8+ to run tests from Rider."); throw new NotSupportedException("Incompatible `Test Framework` package in Unity. Update to v.1.1.8+"); #else var methodInfo = typeof(TestRunnerApi).GetMethod("CancelTestRun"); if (methodInfo == null) methodInfo = typeof(TestRunnerApi).GetMethod("CancelTestRun", BindingFlags.Static | BindingFlags.NonPublic); methodInfo.Invoke(null, new object[] { _sessionGuid }); #endif } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/UnitTesting/RiderTestRunner.cs.meta ================================================ fileFormatVersion: 2 guid: 5c3b27069cb3ddf42ba1260eeefcdd1c MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/UnitTesting/SyncTestRunCallback.cs ================================================ #if TEST_FRAMEWORK using NUnit.Framework.Interfaces; using Packages.Rider.Editor.UnitTesting; using UnityEngine.TestRunner; [assembly: TestRunCallback(typeof(SyncTestRunCallback))] namespace Packages.Rider.Editor.UnitTesting { internal class SyncTestRunCallback : ITestRunCallback { public void RunStarted(ITest testsToRun) { } public void RunFinished(ITestResult testResults) { SyncTestRunEventsHandler.instance.OnRunFinished(); } public void TestStarted(ITest test) { if (!test.IsSuite) SyncTestRunEventsHandler.instance.OnTestStarted(GenerateId(test)); } public void TestFinished(ITestResult result) { if (!result.Test.IsSuite) SyncTestRunEventsHandler.instance.OnTestFinished(); } // https://jetbrains.team/p/net/code/dotnet-libs/files/f04cde7d1dd70ee13bf5532e30f929b9b5ed08a4/ReSharperTestRunner/src/Adapters/TestRunner.Adapters.NUnit3/RemoteTaskDepot.cs?tab=source&line=129 private static string GenerateId(ITest node) { // ES: Parameterized tests defined in a parametrized test fixture, though // constructed for a particular test fixture with the given parameter, have identical fullname that does // not include parameters of parent testfixture (name of the without parameters is used instead). // This leads to 'Test with {id} id is already running' message. var typeName = node.GetType().Name; if (typeName == "ParameterizedMethod" || typeName == "GenericMethod") return $"{node.Parent.FullName}.{node.Name}"; return node.FullName; } } } #endif ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/UnitTesting/SyncTestRunCallback.cs.meta ================================================ fileFormatVersion: 2 guid: 58ab3828fb407c742a48b82bc5983a87 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/UnitTesting/SyncTestRunEventsHandler.cs ================================================ #if TEST_FRAMEWORK using System; using System.Reflection; using UnityEditor; using UnityEngine; namespace Packages.Rider.Editor.UnitTesting { internal class SyncTestRunEventsHandler : ScriptableSingleton { [SerializeField] private string m_SessionId; [SerializeField] private string m_HandlerCodeBase; [SerializeField] private string m_HandlerTypeName; [SerializeField] private string[] m_HandlerDependencies; [SerializeField] private bool m_RunInitialized; private object m_Handler; private MethodInfo m_OnSessionStartedMethodInfo; private MethodInfo m_OnTestStartedMethodInfo; private MethodInfo m_OnTestFinishedMethodInfo; private MethodInfo m_OnSessionFinishedMethodInfo; internal void InitRun(string sessionId, string handlerCodeBase, string handlerTypeName, string[] handlerDependencies) { if (PluginSettings.SelectedLoggingLevel >= LoggingLevel.TRACE) Debug.Log("Rider Test Runner: initializing sync callbacks handler: " + $"sessionId={sessionId}, " + $"codeBase={handlerCodeBase}, " + $"typeName={handlerTypeName}, " + $"dependencies={(handlerDependencies == null ? "" : string.Join("; ", handlerDependencies))}"); m_SessionId = sessionId; m_HandlerCodeBase = handlerCodeBase; m_HandlerTypeName = handlerTypeName; m_HandlerDependencies = handlerDependencies; m_RunInitialized = true; CreateHandlerInstance(); SafeInvokeHandlerMethod(m_OnSessionStartedMethodInfo, Array.Empty()); } private void OnEnable() { if (m_RunInitialized) CreateHandlerInstance(); } internal void OnTestStarted(string testId) { if (m_RunInitialized) SafeInvokeHandlerMethod(m_OnTestStartedMethodInfo, new object[] {testId}); } internal void OnTestFinished() { if (m_RunInitialized) SafeInvokeHandlerMethod(m_OnTestFinishedMethodInfo, Array.Empty()); } internal void OnRunFinished() { if (!m_RunInitialized) return; SafeInvokeHandlerMethod(m_OnSessionFinishedMethodInfo, Array.Empty()); CleanUp(); m_RunInitialized = false; } private void SafeInvokeHandlerMethod(MethodInfo methodInfo, object[] args) { try { methodInfo?.Invoke(m_Handler, args); } catch (Exception e) { Debug.LogException(e); } } private void CreateHandlerInstance() { try { if (m_HandlerDependencies != null) foreach (var dependency in m_HandlerDependencies) { if (PluginSettings.SelectedLoggingLevel >= LoggingLevel.TRACE) Debug.Log($"Rider Test Runner: loading assembly from {dependency}"); Assembly.LoadFrom(dependency); } if (PluginSettings.SelectedLoggingLevel >= LoggingLevel.TRACE) Debug.Log($"Rider Test Runner: loading assembly from {m_HandlerCodeBase}"); var assembly = Assembly.LoadFrom(m_HandlerCodeBase); var type = assembly.GetType(m_HandlerTypeName); if (type == null) { Debug.LogError($"Rider Test Runner: type '{m_HandlerTypeName}' not found in assembly '{assembly.FullName}'"); return; } if (PluginSettings.SelectedLoggingLevel >= LoggingLevel.TRACE) Debug.Log($"Rider Test Runner: creating instance of type '{type.AssemblyQualifiedName}'"); m_Handler = Activator.CreateInstance(type, m_SessionId); m_OnSessionStartedMethodInfo = type.GetMethod("OnSessionStarted", BindingFlags.Instance | BindingFlags.Public); if (m_OnSessionStartedMethodInfo == null) { Debug.LogError($"Rider Test Runner: OnSessionStarted method not found in type='{type.AssemblyQualifiedName}'"); return; } m_OnTestStartedMethodInfo = type.GetMethod("OnTestStarted", BindingFlags.Instance | BindingFlags.Public); if (m_OnTestStartedMethodInfo == null) { Debug.LogError($"Rider Test Runner: OnTestStarted method not found in type='{type.AssemblyQualifiedName}'"); return; } m_OnTestFinishedMethodInfo = type.GetMethod("OnTestFinished", BindingFlags.Instance | BindingFlags.Public); if (m_OnTestFinishedMethodInfo == null) { Debug.LogError($"Rider Test Runner: OnTestFinished method not found in type='{type.AssemblyQualifiedName}'"); return; } m_OnSessionFinishedMethodInfo = type.GetMethod("OnSessionFinished", BindingFlags.Instance | BindingFlags.Public); if (m_OnSessionFinishedMethodInfo == null) Debug.LogError($"Rider Test Runner: OnSessionFinished method not found in type='{type.AssemblyQualifiedName}'"); } catch (Exception e) { Debug.LogException(e); } } private void CleanUp() { m_Handler = null; m_OnSessionStartedMethodInfo = null; m_OnSessionFinishedMethodInfo = null; m_OnTestStartedMethodInfo = null; m_OnTestFinishedMethodInfo = null; } } } #endif ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/UnitTesting/SyncTestRunEventsHandler.cs.meta ================================================ fileFormatVersion: 2 guid: 48483563a64de3a4e8690122762055f1 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/UnitTesting/TestEvent.cs ================================================ #if TEST_FRAMEWORK using System; using NUnit.Framework.Interfaces; namespace Packages.Rider.Editor.UnitTesting { /// /// Is used by Rider Unity plugin by reflection /// [Serializable] internal enum EventType { TestStarted, TestFinished, RunFinished, RunStarted } // do not reorder /// /// Is used by Rider Unity plugin by reflection /// [Serializable] internal class TestEvent { public EventType type; public string id; public string assemblyName; public string output; public TestStatus testStatus; public double duration; public string parentId; public TestEvent(EventType type, string id, string assemblyName, string output, double duration, TestStatus testStatus, string parentID) { this.type = type; this.id = id; this.assemblyName = assemblyName; this.output = output; this.testStatus = testStatus; this.duration = duration; parentId = parentID; } } } #endif ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/UnitTesting/TestEvent.cs.meta ================================================ fileFormatVersion: 2 guid: f9413c47b3a14a64e8810ce76d1a6032 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/UnitTesting/TestsCallback.cs ================================================ #if TEST_FRAMEWORK using System; using System.Linq; using System.Text; using UnityEditor.TestTools.TestRunner.Api; using UnityEngine; namespace Packages.Rider.Editor.UnitTesting { internal class TestsCallback : ScriptableObject, IErrorCallbacks { public void RunFinished(ITestResultAdaptor result) { CallbackData.instance.isRider = false; CallbackData.instance.events.Add( new TestEvent(EventType.RunFinished, "", "","", 0, ParseTestStatus(result.TestStatus), "")); CallbackData.instance.RaiseChangedEvent(); } public void RunStarted(ITestAdaptor testsToRun) { CallbackData.instance.events.Add( new TestEvent(EventType.RunStarted, "", "","", 0, NUnit.Framework.Interfaces.TestStatus.Passed, "")); CallbackData.instance.RaiseChangedEvent(); } public void TestStarted(ITestAdaptor result) { // RIDER-69927 "Test not run" status is shown for the test suite when running unit tests for Unity project var method = result.Method ?? result.Children.Select(a=>a.Method).FirstOrDefault(b => b != null); if (method == null) return; CallbackData.instance.events.Add( new TestEvent(EventType.TestStarted, GenerateId(result), method.TypeInfo.Assembly.GetName().Name, "", 0, NUnit.Framework.Interfaces.TestStatus.Passed, GenerateId(result.Parent))); CallbackData.instance.RaiseChangedEvent(); } public void TestFinished(ITestResultAdaptor result) { var method = result.Test.Method ?? result.Children.Select(a=>a.Test.Method).FirstOrDefault(b => b != null); if (method == null) return; CallbackData.instance.events.Add( new TestEvent(EventType.TestFinished, GenerateId(result.Test), method.TypeInfo.Assembly.GetName().Name, ExtractOutput(result), (result.EndTime-result.StartTime).Milliseconds, ParseTestStatus(result.TestStatus), GenerateId(result.Test.Parent))); CallbackData.instance.RaiseChangedEvent(); } public void OnError(string message) { CallbackData.instance.isRider = false; CallbackData.instance.events.Add( new TestEvent(EventType.RunFinished, "", "",message, 0, NUnit.Framework.Interfaces.TestStatus.Failed, "")); CallbackData.instance.RaiseChangedEvent(); } // see explanation in https://jetbrains.team/p/net/code/dotnet-libs/files/f04cde7d1dd70ee13bf5532e30f929b9b5ed08a4/ReSharperTestRunner/src/Adapters/TestRunner.Adapters.NUnit3/RemoteTaskDepot.cs?tab=source&line=129 private static string GenerateId(ITestAdaptor node) { // ES: Parameterized tests defined in a parametrized test fixture, though // constructed for a particular test fixture with the given parameter, have identical fullname that does // not include parameters of parent testfixture (name of the without parameters is used instead). // This leads to 'Test with {id} id is already running' message. if (node.TypeInfo == null) return $"{node.Parent.FullName}.{node.Name}"; return node.FullName; } private static NUnit.Framework.Interfaces.TestStatus ParseTestStatus(TestStatus testStatus) { return (NUnit.Framework.Interfaces.TestStatus)Enum.Parse(typeof(NUnit.Framework.Interfaces.TestStatus), testStatus.ToString()); } private static string ExtractOutput(ITestResultAdaptor testResult) { var stringBuilder = new StringBuilder(); if (testResult.Message != null) { stringBuilder.AppendLine("Message: "); stringBuilder.AppendLine(testResult.Message); } if (!string.IsNullOrEmpty(testResult.Output)) { stringBuilder.AppendLine("Output: "); stringBuilder.AppendLine(testResult.Output); } if (!string.IsNullOrEmpty(testResult.StackTrace)) { stringBuilder.AppendLine("Stacktrace: "); stringBuilder.AppendLine(testResult.StackTrace); } var result = stringBuilder.ToString(); if (result.Length > 0) return result; return testResult.Output ?? string.Empty; } } } #endif ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/UnitTesting/TestsCallback.cs.meta ================================================ fileFormatVersion: 2 guid: 58aa570dbe0761f43b25ff6c2265bbe2 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/UnitTesting.meta ================================================ fileFormatVersion: 2 guid: a52391bc44c477f40a547ed4ef3b9560 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Util/CommandLineParser.cs ================================================ using System.Collections.Generic; namespace Packages.Rider.Editor.Util { internal class CommandLineParser { public Dictionary Options = new Dictionary(); public CommandLineParser(string[] args) { var i = 0; while (i < args.Length) { var arg = args[i]; if (!arg.StartsWith("-")) { i++; continue; } string value = null; if (i + 1 < args.Length && !args[i + 1].StartsWith("-")) { value = args[i + 1]; i++; } if (!(Options.ContainsKey(arg))) { Options.Add(arg, value); } i++; } } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Util/CommandLineParser.cs.meta ================================================ fileFormatVersion: 2 guid: 154ace4bd16de9f4e84052ac257786d6 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Util/FileSystemUtil.cs ================================================ using System; using System.ComponentModel; using System.IO; using System.Text; using JetBrains.Annotations; using UnityEngine; namespace Packages.Rider.Editor.Util { internal static class FileSystemUtil { [NotNull] public static string GetFinalPathName([NotNull] string path) { if (path == null) throw new ArgumentNullException("path"); // up to MAX_PATH. MAX_PATH on Linux currently 4096, on Mac OS X 1024 // doc: http://man7.org/linux/man-pages/man3/realpath.3.html var sb = new StringBuilder(8192); var result = LibcNativeInterop.realpath(path, sb); if (result == IntPtr.Zero) { throw new Win32Exception($"{path} was not resolved."); } return new FileInfo(sb.ToString()).FullName; } public static string FileNameWithoutExtension(string path) { if (string.IsNullOrEmpty(path)) { return ""; } var indexOfDot = -1; var indexOfSlash = 0; for (var i = path.Length - 1; i >= 0; i--) { if (indexOfDot == -1 && path[i] == '.') { indexOfDot = i; } if (indexOfSlash == 0 && path[i] == '/' || path[i] == '\\') { indexOfSlash = i + 1; break; } } if (indexOfDot == -1) { indexOfDot = path.Length; } return path.Substring(indexOfSlash, indexOfDot - indexOfSlash); } public static bool EditorPathExists(string editorPath) { return SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX && new DirectoryInfo(editorPath).Exists || SystemInfo.operatingSystemFamily != OperatingSystemFamily.MacOSX && new FileInfo(editorPath).Exists; } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Util/FileSystemUtil.cs.meta ================================================ fileFormatVersion: 2 guid: bdbd564a9fdad0b738e76d030cad1204 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Util/LibcNativeInterop.cs ================================================ using System; using System.Runtime.InteropServices; using System.Text; namespace Packages.Rider.Editor.Util { internal static class LibcNativeInterop { [DllImport("libc", SetLastError = true)] public static extern IntPtr realpath(string path, StringBuilder resolved_path); } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Util/LibcNativeInterop.cs.meta ================================================ fileFormatVersion: 2 guid: 071c17858dc6c47ada7b2a1f1ded5402 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Util/RiderMenu.cs ================================================ using JetBrains.Annotations; using Packages.Rider.Editor; using Unity.CodeEditor; // ReSharper disable once CheckNamespace namespace JetBrains.Rider.Unity.Editor { /// /// Is called via commandline from Rider Notification after checking out from source control. /// [UsedImplicitly] public static class RiderMenu { /// /// Is called via commandline from Rider Notification after checking out from source control. /// [UsedImplicitly] public static void MenuOpenProject() { if (RiderScriptEditor.IsRiderOrFleetInstallation(RiderScriptEditor.CurrentEditor)) { // Force the project files to be sync CodeEditor.CurrentEditor.SyncAll(); // Load Project CodeEditor.CurrentEditor.OpenProject(); } } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Util/RiderMenu.cs.meta ================================================ fileFormatVersion: 2 guid: a8860c53ca4073d4f92c403e709c12ba MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Util/RiderPathUtil.cs ================================================ using System.IO; namespace Rider.Editor.Util { internal static class RiderPathUtil { public static bool IsRiderDevEditor(string editorPath) { if (editorPath == null) return false; return "rider-dev".Equals(Path.GetFileNameWithoutExtension(editorPath)); } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Util/RiderPathUtil.cs.meta ================================================ fileFormatVersion: 2 guid: 4d34a9510a5b9d21381c93e73867a193 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Util/SerializableVersion.cs ================================================ using System; namespace Packages.Rider.Editor.Util { [Serializable] internal class SerializableVersion { public SerializableVersion(Version version) { Major = version.Major; Minor = version.Minor; if (version.Build >= 0) Build = version.Build; if (version.Revision >= 0) Revision = version.Revision; } public int Build; public int Major; public int Minor; public int Revision; } internal static class VersionExtension { public static SerializableVersion ToSerializableVersion(this Version version) { if (version == null) return null; return new SerializableVersion(version); } public static Version ToVersion(this SerializableVersion serializableVersion) { if (serializableVersion == null) return null; var build = serializableVersion.Build; if (build < 0) build = 0; var revision = serializableVersion.Revision; if (revision < 0) revision = 0; return new Version(serializableVersion.Major, serializableVersion.Minor, build, revision); } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Util/SerializableVersion.cs.meta ================================================ fileFormatVersion: 2 guid: 8e1f00a9be2eb66438e2835fdf1ea11b MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Util/StringBuilderExtensions.cs ================================================ using System.Collections.Generic; using System.Text; namespace Packages.Rider.Editor.Util { internal static class StringBuilderExtensions { // StringBuilder.AppendJoin is very useful, but not available in 2019.2 // It requires netstandard 2.1 public static StringBuilder CompatibleAppendJoin(this StringBuilder stringBuilder, char separator, IEnumerable parts) { var first = true; foreach (var part in parts) { if (!first) stringBuilder.Append(separator); stringBuilder.Append(part); first = false; } return stringBuilder; } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Util/StringBuilderExtensions.cs.meta ================================================ fileFormatVersion: 2 guid: 302b1bdfb7a645db994491799832912b timeCreated: 1699444126 ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Util/StringUtils.cs ================================================ using System.IO; namespace Packages.Rider.Editor.Util { internal static class StringUtils { public static string NormalizePath(this string path) { return path.Replace(Path.DirectorySeparatorChar == '\\' ? '/' : '\\', Path.DirectorySeparatorChar); } } } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Util/StringUtils.cs.meta ================================================ fileFormatVersion: 2 guid: b0221f76dde941d492827802c9b7c1f9 timeCreated: 1623056718 ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/Util.meta ================================================ fileFormatVersion: 2 guid: 5e726086cd652f82087d59d67d2c24cd folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/com.unity.ide.rider.asmdef ================================================ { "name": "Unity.Rider.Editor", "rootNamespace": "Packages", "references": [ "GUID:0acc523941302664db1f4e527237feb3", "GUID:27619889b8ba8c24980f49ee34dbb44a" ], "includePlatforms": [ "Editor" ], "excludePlatforms": [], "allowUnsafeCode": false, "overrideReferences": true, "precompiledReferences": [ "nunit.framework.dll", "JetBrains.Rider.PathLocator.dll" ], "autoReferenced": true, "defineConstraints": [ "UNITY_2019_2_OR_NEWER" ], "versionDefines": [ { "name": "com.unity.test-framework", "expression": "1.1.8", "define": "TEST_FRAMEWORK" }, { "name": "Unity", "expression": "2021.2.0a9", "define": "ROSLYN_ANALYZER_FIX" }, { "name": "Unity", "expression": "[2021.1.2f1,2021.2.0a1]", "define": "ROSLYN_ANALYZER_FIX" }, { "name": "Unity", "expression": "[2020.3.6f1,2021.0]", "define": "ROSLYN_ANALYZER_FIX" } ], "noEngineReferences": false } ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor/com.unity.ide.rider.asmdef.meta ================================================ fileFormatVersion: 2 guid: d528c8c98d269ca44a06cd9624a03945 AssemblyDefinitionImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider/Editor.meta ================================================ fileFormatVersion: 2 guid: 1b393f6b29a9ee84c803af1ab4944b71 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/Rider.meta ================================================ fileFormatVersion: 2 guid: 9129183a42052cd43b9c284d6dbd541e folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/com.unity.ide.rider/package.json ================================================ { "name": "com.unity.ide.rider", "displayName": "JetBrains Rider Editor", "description": "The JetBrains Rider Editor package provides an integration for using the JetBrains Rider IDE as a code editor for Unity. It adds support for generating .csproj files for code completion and auto-discovery of installations.", "version": "3.0.31", "unity": "2019.2", "unityRelease": "6f1", "dependencies": { "com.unity.ext.nunit": "1.0.6" }, "relatedPackages": { "com.unity.ide.rider.tests": "3.0.31" }, "_upm": { "changelog": "fix RIDER-104519 Rider is reporting errors in scripts that work fine in Unity when utilizing DOTS - when Player project, by generating projects for all assemblies in \"com.unity.entities\", \"com.unity.collections\"\nfix RIDER-111622 Unity Rider package is not compatible with Rider Dev builds" }, "upmCi": { "footprint": "b575d2acd4f31d85999e18d8204f19ec61b9c179" }, "documentationUrl": "https://docs.unity3d.com/Packages/com.unity.ide.rider@3.0/manual/index.html", "repository": { "url": "https://github.cds.internal.unity3d.com/unity/com.unity.ide.rider.git", "type": "git", "revision": "ccd778a2275ff09f1a83bc924cfb2fb4d5b63566" } } ================================================ FILE: Packages/com.unity.ide.rider/package.json.meta ================================================ fileFormatVersion: 2 guid: 66c95bb3c74257f41bae2622511dc02d TextScriptImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: Packages/manifest.json ================================================ { "dependencies": { "com.unity.ai.navigation": "2.0.4", "com.unity.ide.rider": "3.0.31", "com.unity.ide.visualstudio": "2.0.22", "com.unity.multiplayer.center": "1.0.0", "com.unity.render-pipelines.universal": "17.0.3", "com.unity.timeline": "1.8.7", "com.unity.ugui": "2.0.0", "com.unity.modules.accessibility": "1.0.0", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", "com.unity.modules.animation": "1.0.0", "com.unity.modules.assetbundle": "1.0.0", "com.unity.modules.audio": "1.0.0", "com.unity.modules.cloth": "1.0.0", "com.unity.modules.director": "1.0.0", "com.unity.modules.imageconversion": "1.0.0", "com.unity.modules.imgui": "1.0.0", "com.unity.modules.particlesystem": "1.0.0", "com.unity.modules.physics": "1.0.0", "com.unity.modules.physics2d": "1.0.0", "com.unity.modules.screencapture": "1.0.0", "com.unity.modules.terrain": "1.0.0", "com.unity.modules.terrainphysics": "1.0.0", "com.unity.modules.tilemap": "1.0.0", "com.unity.modules.ui": "1.0.0", "com.unity.modules.uielements": "1.0.0", "com.unity.modules.umbra": "1.0.0", "com.unity.modules.unityanalytics": "1.0.0", "com.unity.modules.unitywebrequest": "1.0.0", "com.unity.modules.unitywebrequestassetbundle": "1.0.0", "com.unity.modules.unitywebrequestaudio": "1.0.0", "com.unity.modules.unitywebrequesttexture": "1.0.0", "com.unity.modules.unitywebrequestwww": "1.0.0", "com.unity.modules.vehicles": "1.0.0", "com.unity.modules.video": "1.0.0", "com.unity.modules.wind": "1.0.0" }, "scopedRegistries": [ { "name": "ET-Packages", "url": "https://npm.pkg.github.com/@ET-Packages", "scopes": [ "cn.etetet" ] } ] } ================================================ FILE: Packages/packages-lock.json ================================================ { "dependencies": { "cn.etetet.actorlocation": { "version": "file:cn.etetet.actorlocation", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.ai": { "version": "file:cn.etetet.ai", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.aoi": { "version": "file:cn.etetet.aoi", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.console": { "version": "file:cn.etetet.console", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.core": { "version": "file:cn.etetet.core", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.demores": { "version": "file:cn.etetet.demores", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.excel": { "version": "file:cn.etetet.excel", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.http": { "version": "file:cn.etetet.http", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.hybridclr": { "version": "file:cn.etetet.hybridclr", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.loader": { "version": "file:cn.etetet.loader", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.login": { "version": "file:cn.etetet.login", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.mathematics": { "version": "file:cn.etetet.mathematics", "depth": 0, "source": "embedded", "dependencies": { "com.unity.mathematics": "1.2.6" } }, "cn.etetet.memorypack": { "version": "file:cn.etetet.memorypack", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.move": { "version": "file:cn.etetet.move", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.netinner": { "version": "file:cn.etetet.netinner", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.numeric": { "version": "file:cn.etetet.numeric", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.packagemanager": { "version": "file:cn.etetet.packagemanager", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.proto": { "version": "file:cn.etetet.proto", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.recast": { "version": "file:cn.etetet.recast", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.referencecollector": { "version": "file:cn.etetet.referencecollector", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.router": { "version": "file:cn.etetet.router", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.sourcegenerator": { "version": "file:cn.etetet.sourcegenerator", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.startconfig": { "version": "file:cn.etetet.startconfig", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.statesync": { "version": "file:cn.etetet.statesync", "depth": 0, "source": "embedded", "dependencies": { "cn.etetet.packagemanager": "1.0.0", "cn.etetet.actorlocation": "3.0.0", "cn.etetet.core": "3.0.3", "cn.etetet.loader": "3.0.1", "cn.etetet.demores": "3.0.0", "cn.etetet.mathematics": "0.0.2", "cn.etetet.router": "3.0.0", "cn.etetet.console": "3.0.0", "cn.etetet.unit": "3.0.0", "cn.etetet.http": "3.0.0", "cn.etetet.recast": "3.0.0", "cn.etetet.numeric": "3.0.0", "cn.etetet.move": "3.0.0", "cn.etetet.watcher": "3.0.0", "cn.etetet.aoi": "3.0.0", "cn.etetet.ui": "3.0.0", "cn.etetet.referencecollector": "3.0.0", "cn.etetet.login": "3.0.0", "cn.etetet.netinner": "3.0.0", "cn.etetet.hybridclr": "7.8.0", "cn.etetet.excel": "3.0.0", "cn.etetet.proto": "3.0.2", "cn.etetet.ai": "3.0.0", "cn.etetet.sourcegenerator": "3.0.1", "cn.etetet.yooassets": "2.1.5", "cn.etetet.memorypack": "1.10.1", "cn.etetet.startconfig": "3.0.0" } }, "cn.etetet.ui": { "version": "file:cn.etetet.ui", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.unit": { "version": "file:cn.etetet.unit", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.watcher": { "version": "file:cn.etetet.watcher", "depth": 0, "source": "embedded", "dependencies": {} }, "cn.etetet.yooassets": { "version": "file:cn.etetet.yooassets", "depth": 0, "source": "embedded", "dependencies": { "com.unity.scriptablebuildpipeline": "1.21.21", "com.unity.modules.assetbundle": "1.0.0", "com.unity.modules.unitywebrequest": "1.0.0", "com.unity.modules.unitywebrequestassetbundle": "1.0.0" } }, "com.etetet.init": { "version": "file:com.etetet.init", "depth": 0, "source": "embedded", "dependencies": {} }, "com.halodi.halodi-unity-package-registry-manager": { "version": "file:com.halodi.halodi-unity-package-registry-manager", "depth": 0, "source": "embedded", "dependencies": { "com.unity.nuget.newtonsoft-json": "3.0.2", "com.unity.sharp-zip-lib": "1.3.4-preview" } }, "com.unity.ai.navigation": { "version": "2.0.4", "depth": 0, "source": "registry", "dependencies": { "com.unity.modules.ai": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.burst": { "version": "1.8.18", "depth": 2, "source": "registry", "dependencies": { "com.unity.mathematics": "1.2.1", "com.unity.modules.jsonserialize": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.collections": { "version": "2.5.1", "depth": 2, "source": "registry", "dependencies": { "com.unity.burst": "1.8.17", "com.unity.test-framework": "1.4.5", "com.unity.nuget.mono-cecil": "1.11.4", "com.unity.test-framework.performance": "3.0.3" }, "url": "https://packages.unity.com" }, "com.unity.ext.nunit": { "version": "2.0.5", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.ide.rider": { "version": "file:com.unity.ide.rider", "depth": 0, "source": "embedded", "dependencies": { "com.unity.ext.nunit": "1.0.6" } }, "com.unity.ide.visualstudio": { "version": "2.0.22", "depth": 0, "source": "registry", "dependencies": { "com.unity.test-framework": "1.1.9" }, "url": "https://packages.unity.com" }, "com.unity.mathematics": { "version": "1.3.2", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.multiplayer.center": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": { "com.unity.modules.uielements": "1.0.0" } }, "com.unity.nuget.mono-cecil": { "version": "1.11.4", "depth": 3, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.nuget.newtonsoft-json": { "version": "3.2.1", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.render-pipelines.core": { "version": "17.0.3", "depth": 1, "source": "builtin", "dependencies": { "com.unity.burst": "1.8.14", "com.unity.mathematics": "1.3.2", "com.unity.ugui": "2.0.0", "com.unity.collections": "2.4.3", "com.unity.modules.physics": "1.0.0", "com.unity.modules.terrain": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0", "com.unity.rendering.light-transport": "1.0.1" } }, "com.unity.render-pipelines.universal": { "version": "17.0.3", "depth": 0, "source": "builtin", "dependencies": { "com.unity.render-pipelines.core": "17.0.3", "com.unity.shadergraph": "17.0.3", "com.unity.render-pipelines.universal-config": "17.0.3" } }, "com.unity.render-pipelines.universal-config": { "version": "17.0.3", "depth": 1, "source": "builtin", "dependencies": { "com.unity.render-pipelines.core": "17.0.3" } }, "com.unity.rendering.light-transport": { "version": "1.0.1", "depth": 2, "source": "builtin", "dependencies": { "com.unity.collections": "2.2.0", "com.unity.mathematics": "1.2.4", "com.unity.modules.terrain": "1.0.0" } }, "com.unity.scriptablebuildpipeline": { "version": "2.1.4", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.searcher": { "version": "4.9.2", "depth": 2, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.shadergraph": { "version": "17.0.3", "depth": 1, "source": "builtin", "dependencies": { "com.unity.render-pipelines.core": "17.0.3", "com.unity.searcher": "4.9.2" } }, "com.unity.sharp-zip-lib": { "version": "1.3.8", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.test-framework": { "version": "1.4.5", "depth": 1, "source": "registry", "dependencies": { "com.unity.ext.nunit": "2.0.3", "com.unity.modules.imgui": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.test-framework.performance": { "version": "3.0.3", "depth": 3, "source": "registry", "dependencies": { "com.unity.test-framework": "1.1.31", "com.unity.modules.jsonserialize": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.timeline": { "version": "1.8.7", "depth": 0, "source": "registry", "dependencies": { "com.unity.modules.audio": "1.0.0", "com.unity.modules.director": "1.0.0", "com.unity.modules.animation": "1.0.0", "com.unity.modules.particlesystem": "1.0.0" }, "url": "https://packages.unity.com" }, "com.unity.ugui": { "version": "2.0.0", "depth": 0, "source": "builtin", "dependencies": { "com.unity.modules.ui": "1.0.0", "com.unity.modules.imgui": "1.0.0" } }, "com.unity.modules.accessibility": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} }, "com.unity.modules.ai": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} }, "com.unity.modules.androidjni": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} }, "com.unity.modules.animation": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} }, "com.unity.modules.assetbundle": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} }, "com.unity.modules.audio": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} }, "com.unity.modules.cloth": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": { "com.unity.modules.physics": "1.0.0" } }, "com.unity.modules.director": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": { "com.unity.modules.audio": "1.0.0", "com.unity.modules.animation": "1.0.0" } }, "com.unity.modules.hierarchycore": { "version": "1.0.0", "depth": 1, "source": "builtin", "dependencies": {} }, "com.unity.modules.imageconversion": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} }, "com.unity.modules.imgui": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} }, "com.unity.modules.jsonserialize": { "version": "1.0.0", "depth": 1, "source": "builtin", "dependencies": {} }, "com.unity.modules.particlesystem": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} }, "com.unity.modules.physics": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} }, "com.unity.modules.physics2d": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} }, "com.unity.modules.screencapture": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": { "com.unity.modules.imageconversion": "1.0.0" } }, "com.unity.modules.terrain": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} }, "com.unity.modules.terrainphysics": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": { "com.unity.modules.physics": "1.0.0", "com.unity.modules.terrain": "1.0.0" } }, "com.unity.modules.tilemap": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": { "com.unity.modules.physics2d": "1.0.0" } }, "com.unity.modules.ui": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} }, "com.unity.modules.uielements": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": { "com.unity.modules.ui": "1.0.0", "com.unity.modules.imgui": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0", "com.unity.modules.hierarchycore": "1.0.0" } }, "com.unity.modules.umbra": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} }, "com.unity.modules.unityanalytics": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": { "com.unity.modules.unitywebrequest": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0" } }, "com.unity.modules.unitywebrequest": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} }, "com.unity.modules.unitywebrequestassetbundle": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": { "com.unity.modules.assetbundle": "1.0.0", "com.unity.modules.unitywebrequest": "1.0.0" } }, "com.unity.modules.unitywebrequestaudio": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": { "com.unity.modules.unitywebrequest": "1.0.0", "com.unity.modules.audio": "1.0.0" } }, "com.unity.modules.unitywebrequesttexture": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": { "com.unity.modules.unitywebrequest": "1.0.0", "com.unity.modules.imageconversion": "1.0.0" } }, "com.unity.modules.unitywebrequestwww": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": { "com.unity.modules.unitywebrequest": "1.0.0", "com.unity.modules.unitywebrequestassetbundle": "1.0.0", "com.unity.modules.unitywebrequestaudio": "1.0.0", "com.unity.modules.audio": "1.0.0", "com.unity.modules.assetbundle": "1.0.0", "com.unity.modules.imageconversion": "1.0.0" } }, "com.unity.modules.vehicles": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": { "com.unity.modules.physics": "1.0.0" } }, "com.unity.modules.video": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": { "com.unity.modules.audio": "1.0.0", "com.unity.modules.ui": "1.0.0", "com.unity.modules.unitywebrequest": "1.0.0" } }, "com.unity.modules.wind": { "version": "1.0.0", "depth": 0, "source": "builtin", "dependencies": {} } } } ================================================ FILE: ProjectSettings/AudioManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!11 &1 AudioManager: m_ObjectHideFlags: 0 m_Volume: 1 Rolloff Scale: 1 Doppler Factor: 1 Default Speaker Mode: 2 m_SampleRate: 0 m_DSPBufferSize: 0 m_VirtualVoiceCount: 512 m_RealVoiceCount: 32 m_SpatializerPlugin: m_DisableAudio: 0 m_VirtualizeEffects: 1 ================================================ FILE: ProjectSettings/AutoStreamingSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!1200 &1 AutoStreamingSettings: m_ObjectHideFlags: 0 serializedVersion: 2 mSearchMode: 7 mCustomSearchFile: mTextures: [] mAudios: [] mMeshes: [] mScenes: [] mConfigCCD: useCCD: 0 cosKey: projectGuid: bucketUuid: bucketName: badgeName: ================================================ FILE: ProjectSettings/BurstAotSettings_Android.json ================================================ { "MonoBehaviour": { "Version": 4, "EnableBurstCompilation": true, "EnableOptimisations": true, "EnableSafetyChecks": false, "EnableDebugInAllBuilds": false, "EnableArmv9SecurityFeatures": false, "CpuMinTargetX32": 0, "CpuMaxTargetX32": 0, "CpuMinTargetX64": 0, "CpuMaxTargetX64": 0, "CpuTargetsArm64": 512, "OptimizeFor": 0 } } ================================================ FILE: ProjectSettings/BurstAotSettings_StandaloneOSX.json ================================================ { "MonoBehaviour": { "Version": 4, "EnableBurstCompilation": true, "EnableOptimisations": true, "EnableSafetyChecks": false, "EnableDebugInAllBuilds": false, "UsePlatformSDKLinker": false, "CpuMinTargetX32": 0, "CpuMaxTargetX32": 0, "CpuMinTargetX64": 0, "CpuMaxTargetX64": 0, "CpuTargetsX64": 72, "OptimizeFor": 0 } } ================================================ FILE: ProjectSettings/BurstAotSettings_StandaloneWindows.json ================================================ { "MonoBehaviour": { "Version": 4, "EnableBurstCompilation": true, "EnableOptimisations": true, "EnableSafetyChecks": false, "EnableDebugInAllBuilds": false, "UsePlatformSDKLinker": false, "CpuMinTargetX32": 0, "CpuMaxTargetX32": 0, "CpuMinTargetX64": 0, "CpuMaxTargetX64": 0, "CpuTargetsX32": 6, "CpuTargetsX64": 72, "OptimizeFor": 0 } } ================================================ FILE: ProjectSettings/BurstAotSettings_iOS.json ================================================ { "MonoBehaviour": { "Version": 4, "EnableBurstCompilation": true, "EnableOptimisations": true, "EnableSafetyChecks": false, "EnableDebugInAllBuilds": false, "EnableArmv9SecurityFeatures": false, "CpuMinTargetX32": 0, "CpuMaxTargetX32": 0, "CpuMinTargetX64": 0, "CpuMaxTargetX64": 0, "OptimizeFor": 0 } } ================================================ FILE: ProjectSettings/ClusterInputManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!236 &1 ClusterInputManager: m_ObjectHideFlags: 0 m_Inputs: [] ================================================ FILE: ProjectSettings/CommonBurstAotSettings.json ================================================ { "MonoBehaviour": { "Version": 4, "DisabledWarnings": "" } } ================================================ FILE: ProjectSettings/DynamicsManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!55 &1 PhysicsManager: m_ObjectHideFlags: 0 serializedVersion: 2 m_Gravity: {x: 0, y: -9.81, z: 0} m_DefaultMaterial: {fileID: 0} m_BounceThreshold: 2 m_SleepThreshold: 0.005 m_DefaultContactOffset: 0.01 m_SolverIterationCount: 6 m_SolverVelocityIterations: 1 m_QueriesHitTriggers: 1 m_EnableAdaptiveForce: 0 m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ================================================ FILE: ProjectSettings/EditorBuildSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!1045 &1 EditorBuildSettings: m_ObjectHideFlags: 0 serializedVersion: 2 m_Scenes: - enabled: 1 path: Packages/cn.etetet.loader/Scenes/Init.unity guid: e0d691ac8c1d0454ba07089ea820e18a m_configObjects: com.unity.addressableassets: {fileID: 11400000, guid: f989b2ba24890344e858b377390e01f5, type: 2} ================================================ FILE: ProjectSettings/EditorSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!159 &1 EditorSettings: m_ObjectHideFlags: 0 serializedVersion: 11 m_SerializationMode: 2 m_LineEndingsForNewScripts: 1 m_DefaultBehaviorMode: 0 m_PrefabRegularEnvironment: {fileID: 0} m_PrefabUIEnvironment: {fileID: 0} m_SpritePackerMode: 5 m_SpritePackerPaddingPower: 1 m_Bc7TextureCompressor: 0 m_EtcTextureCompressorBehavior: 0 m_EtcTextureFastCompressor: 2 m_EtcTextureNormalCompressor: 2 m_EtcTextureBestCompressor: 5 m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef;asmref m_ProjectGenerationRootNamespace: ET m_EnableTextureStreamingInEditMode: 1 m_EnableTextureStreamingInPlayMode: 1 m_AsyncShaderCompilation: 1 m_CachingShaderPreprocessor: 1 m_PrefabModeAllowAutoSave: 1 m_EnterPlayModeOptionsEnabled: 0 m_EnterPlayModeOptions: 3 m_GameObjectNamingDigits: 1 m_GameObjectNamingScheme: 0 m_AssetNamingUsesSpace: 1 m_UseLegacyProbeSampleCount: 1 m_SerializeInlineMappingsOnOneLine: 0 m_DisableCookiesInLightmapper: 1 m_AssetPipelineMode: 1 m_RefreshImportMode: 0 m_CacheServerMode: 0 m_CacheServerEndpoint: m_CacheServerNamespacePrefix: default m_CacheServerEnableDownload: 1 m_CacheServerEnableUpload: 1 m_CacheServerEnableAuth: 0 m_CacheServerEnableTls: 0 m_CacheServerValidationMode: 2 ================================================ FILE: ProjectSettings/GraphicsSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!30 &1 GraphicsSettings: m_ObjectHideFlags: 0 serializedVersion: 16 m_Deferred: m_Mode: 1 m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0} m_DeferredReflections: m_Mode: 1 m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0} m_ScreenSpaceShadows: m_Mode: 1 m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0} m_DepthNormals: m_Mode: 1 m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0} m_MotionVectors: m_Mode: 1 m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0} m_LightHalo: m_Mode: 1 m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0} m_LensFlare: m_Mode: 1 m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0} m_VideoShadersIncludeMode: 2 m_AlwaysIncludedShaders: - {fileID: 7, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 10782, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 16000, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 17000, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 16001, guid: 0000000000000000f000000000000000, type: 0} m_PreloadedShaders: [] m_PreloadShadersBatchTimeLimit: -1 m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0} m_CustomRenderPipeline: {fileID: 11400000, guid: 313955cd6271f5c4985cba4adc30fc8e, type: 2} m_TransparencySortMode: 0 m_TransparencySortAxis: {x: 0, y: 0, z: 1} m_DefaultRenderingPath: 1 m_DefaultMobileRenderingPath: 1 m_TierSettings: [] m_LightmapStripping: 1 m_FogStripping: 1 m_InstancingStripping: 2 m_BrgStripping: 0 m_LightmapKeepPlain: 1 m_LightmapKeepDirCombined: 0 m_LightmapKeepDynamicPlain: 0 m_LightmapKeepDynamicDirCombined: 0 m_LightmapKeepShadowMask: 0 m_LightmapKeepSubtractive: 0 m_FogKeepLinear: 0 m_FogKeepExp: 0 m_FogKeepExp2: 0 m_AlbedoSwatchInfos: [] m_RenderPipelineGlobalSettingsMap: UnityEngine.Rendering.Universal.UniversalRenderPipeline: {fileID: 11400000, guid: 7995cfbbc1fea9d49b9d9e9b1a4bee57, type: 2} m_LightsUseLinearIntensity: 1 m_LightsUseColorTemperature: 1 m_LogWhenShaderIsCompiled: 0 m_LightProbeOutsideHullStrategy: 0 m_CameraRelativeLightCulling: 0 m_CameraRelativeShadowCulling: 0 ================================================ FILE: ProjectSettings/HybridCLRSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!114 &1 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: e189374413a3f00468e49d51d8b27a09, type: 3} m_Name: m_EditorClassIdentifier: enable: 1 useGlobalIl2cpp: 0 hybridclrRepoURL: https://gitee.com/focus-creative-games/hybridclr il2cppPlusRepoURL: https://gitee.com/focus-creative-games/il2cpp_plus hotUpdateAssemblyDefinitions: [] hotUpdateAssemblies: - ET.Model - ET.Hotfix - ET.ModelView - ET.HotfixView preserveHotUpdateAssemblies: [] hotUpdateDllCompileOutputRootDir: HybridCLRData/HotUpdateDlls externalHotUpdateAssembliyDirs: - Temp/Bin/Debug strippedAOTDllOutputRootDir: HybridCLRData/AssembliesPostIl2CppStrip patchAOTAssemblies: - ET.Loader.dll - ET.Core.dll - ET.YooAssets.dll - MongoDB.Bson.dll - CommandLine.dll - System.dll - System.Core.dll - mscorlib.dll - ET.MemoryPack.dll - System.Runtime.CompilerServices.Unsafe.dll outputLinkFile: HybridCLR/link.xml outputAOTGenericReferenceFile: HybridCLR/AOTGenericReferences.cs maxGenericReferenceIteration: 10 maxMethodBridgeGenericIteration: 10 ================================================ FILE: ProjectSettings/InputManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!13 &1 InputManager: m_ObjectHideFlags: 0 serializedVersion: 2 m_Axes: - serializedVersion: 3 m_Name: Horizontal descriptiveName: descriptiveNegativeName: negativeButton: left positiveButton: right altNegativeButton: a altPositiveButton: d gravity: 3 dead: 0.001 sensitivity: 3 snap: 1 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Vertical descriptiveName: descriptiveNegativeName: negativeButton: down positiveButton: up altNegativeButton: s altPositiveButton: w gravity: 3 dead: 0.001 sensitivity: 3 snap: 1 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Fire1 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: left ctrl altNegativeButton: altPositiveButton: mouse 0 gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Fire2 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: left alt altNegativeButton: altPositiveButton: mouse 1 gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Fire3 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: left cmd altNegativeButton: altPositiveButton: mouse 2 gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Jump descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: space altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Mouse X descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: altNegativeButton: altPositiveButton: gravity: 0 dead: 0 sensitivity: 0.1 snap: 0 invert: 0 type: 1 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Mouse Y descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: altNegativeButton: altPositiveButton: gravity: 0 dead: 0 sensitivity: 0.1 snap: 0 invert: 0 type: 1 axis: 1 joyNum: 0 - serializedVersion: 3 m_Name: Mouse ScrollWheel descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: altNegativeButton: altPositiveButton: gravity: 0 dead: 0 sensitivity: 0.1 snap: 0 invert: 0 type: 1 axis: 2 joyNum: 0 - serializedVersion: 3 m_Name: Horizontal descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: altNegativeButton: altPositiveButton: gravity: 0 dead: 0.19 sensitivity: 1 snap: 0 invert: 0 type: 2 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Vertical descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: altNegativeButton: altPositiveButton: gravity: 0 dead: 0.19 sensitivity: 1 snap: 0 invert: 1 type: 2 axis: 1 joyNum: 0 - serializedVersion: 3 m_Name: Fire1 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: joystick button 0 altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Fire2 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: joystick button 1 altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Fire3 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: joystick button 2 altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Jump descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: joystick button 3 altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Submit descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: return altNegativeButton: altPositiveButton: joystick button 0 gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Submit descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: enter altNegativeButton: altPositiveButton: space gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Cancel descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: escape altNegativeButton: altPositiveButton: joystick button 1 gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Enable Debug Button 1 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: left ctrl altNegativeButton: altPositiveButton: joystick button 8 gravity: 0 dead: 0 sensitivity: 0 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Enable Debug Button 2 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: backspace altNegativeButton: altPositiveButton: joystick button 9 gravity: 0 dead: 0 sensitivity: 0 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Debug Reset descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: left alt altNegativeButton: altPositiveButton: joystick button 1 gravity: 0 dead: 0 sensitivity: 0 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Debug Next descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: page down altNegativeButton: altPositiveButton: joystick button 5 gravity: 0 dead: 0 sensitivity: 0 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Debug Previous descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: page up altNegativeButton: altPositiveButton: joystick button 4 gravity: 0 dead: 0 sensitivity: 0 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Debug Validate descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: return altNegativeButton: altPositiveButton: joystick button 0 gravity: 0 dead: 0 sensitivity: 0 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Debug Persistent descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: right shift altNegativeButton: altPositiveButton: joystick button 2 gravity: 0 dead: 0 sensitivity: 0 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Debug Multiplier descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: left shift altNegativeButton: altPositiveButton: joystick button 3 gravity: 0 dead: 0 sensitivity: 0 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Debug Horizontal descriptiveName: descriptiveNegativeName: negativeButton: left positiveButton: right altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Debug Vertical descriptiveName: descriptiveNegativeName: negativeButton: down positiveButton: up altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Debug Vertical descriptiveName: descriptiveNegativeName: negativeButton: down positiveButton: up altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 2 axis: 6 joyNum: 0 - serializedVersion: 3 m_Name: Debug Horizontal descriptiveName: descriptiveNegativeName: negativeButton: left positiveButton: right altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 2 axis: 5 joyNum: 0 ================================================ FILE: ProjectSettings/MemorySettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!387306366 &1 MemorySettings: m_ObjectHideFlags: 0 m_EditorMemorySettings: m_MainAllocatorBlockSize: -1 m_ThreadAllocatorBlockSize: -1 m_MainGfxBlockSize: -1 m_ThreadGfxBlockSize: -1 m_CacheBlockSize: -1 m_TypetreeBlockSize: -1 m_ProfilerBlockSize: -1 m_ProfilerEditorBlockSize: -1 m_BucketAllocatorGranularity: -1 m_BucketAllocatorBucketsCount: -1 m_BucketAllocatorBlockSize: -1 m_BucketAllocatorBlockCount: -1 m_ProfilerBucketAllocatorGranularity: -1 m_ProfilerBucketAllocatorBucketsCount: -1 m_ProfilerBucketAllocatorBlockSize: -1 m_ProfilerBucketAllocatorBlockCount: -1 m_TempAllocatorSizeMain: -1 m_JobTempAllocatorBlockSize: -1 m_BackgroundJobTempAllocatorBlockSize: -1 m_JobTempAllocatorReducedBlockSize: -1 m_TempAllocatorSizeGIBakingWorker: -1 m_TempAllocatorSizeNavMeshWorker: -1 m_TempAllocatorSizeAudioWorker: -1 m_TempAllocatorSizeCloudWorker: -1 m_TempAllocatorSizeGfx: -1 m_TempAllocatorSizeJobWorker: -1 m_TempAllocatorSizeBackgroundWorker: -1 m_TempAllocatorSizePreloadManager: -1 m_PlatformMemorySettings: {} ================================================ FILE: ProjectSettings/MultiplayerManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!655991488 &1 MultiplayerManager: m_ObjectHideFlags: 0 m_EnableMultiplayerRoles: 0 m_StrippingTypes: {} ================================================ FILE: ProjectSettings/NavMeshAreas.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!126 &1 NavMeshProjectSettings: m_ObjectHideFlags: 0 serializedVersion: 2 areas: - name: Walkable cost: 1 - name: Not Walkable cost: 1 - name: Jump cost: 2 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 m_LastAgentTypeID: -1372625422 m_Settings: - serializedVersion: 2 agentTypeID: 0 agentRadius: 0.5 agentHeight: 2 agentSlope: 45 agentClimb: 0.75 ledgeDropHeight: 0 maxJumpAcrossDistance: 0 minRegionArea: 2 manualCellSize: 0 cellSize: 0.16666667 manualTileSize: 0 tileSize: 256 accuratePlacement: 0 maxJobWorkers: 0 preserveTilesOutsideBounds: 0 debug: m_Flags: 0 m_SettingNames: - Humanoid ================================================ FILE: ProjectSettings/NetworkManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!149 &1 NetworkManager: m_ObjectHideFlags: 0 m_DebugLevel: 0 m_Sendrate: 15 m_AssetToPrefab: {} ================================================ FILE: ProjectSettings/PackageManagerSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!114 &1 MonoBehaviour: m_ObjectHideFlags: 61 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0} m_Name: m_EditorClassIdentifier: m_EnablePreReleasePackages: 0 m_AdvancedSettingsExpanded: 1 m_ScopedRegistriesSettingsExpanded: 1 m_SeeAllPackageVersions: 0 m_DismissPreviewPackagesInUse: 0 oneTimeWarningShown: 1 m_Registries: - m_Id: main m_Name: m_Url: https://packages.unity.com m_Scopes: [] m_IsDefault: 1 m_Capabilities: 7 m_ConfigSource: 0 - m_Id: scoped:project:ET-Packages m_Name: ET-Packages m_Url: https://npm.pkg.github.com/@ET-Packages m_Scopes: - cn.etetet m_IsDefault: 0 m_Capabilities: 0 m_ConfigSource: 4 m_UserSelectedRegistryName: ET-Packages m_UserAddingNewScopedRegistry: 0 m_RegistryInfoDraft: m_Modified: 0 m_ErrorMessage: m_UserModificationsInstanceId: -838 m_OriginalInstanceId: -842 m_LoadAssets: 0 ================================================ FILE: ProjectSettings/Physics2DSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!19 &1 Physics2DSettings: m_ObjectHideFlags: 0 serializedVersion: 2 m_Gravity: {x: 0, y: -9.81} m_DefaultMaterial: {fileID: 0} m_VelocityIterations: 8 m_PositionIterations: 3 m_VelocityThreshold: 1 m_MaxLinearCorrection: 0.2 m_MaxAngularCorrection: 8 m_MaxTranslationSpeed: 100 m_MaxRotationSpeed: 360 m_MinPenetrationForPenalty: 0.01 m_BaumgarteScale: 0.2 m_BaumgarteTimeOfImpactScale: 0.75 m_TimeToSleep: 0.5 m_LinearSleepTolerance: 0.01 m_AngularSleepTolerance: 2 m_QueriesHitTriggers: 1 m_QueriesStartInColliders: 1 m_ChangeStopsCallbacks: 1 m_AlwaysShowColliders: 0 m_ShowColliderSleep: 1 m_ShowColliderContacts: 0 m_ContactArrowScale: 0.2 m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412} m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432} m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745} m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ================================================ FILE: ProjectSettings/PresetManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!1386491679 &1 PresetManager: m_ObjectHideFlags: 0 m_DefaultList: [] ================================================ FILE: ProjectSettings/ProjectSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!129 &1 PlayerSettings: m_ObjectHideFlags: 0 serializedVersion: 28 productGUID: 1eb5b450436966b49a962ffe89a9d776 AndroidProfiler: 0 AndroidFilterTouchesWhenObscured: 0 AndroidEnableSustainedPerformanceMode: 0 defaultScreenOrientation: 4 targetDevice: 2 useOnDemandResources: 0 accelerometerFrequency: 60 companyName: test productName: ET defaultCursor: {fileID: 0} cursorHotspot: {x: 0, y: 0} m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} m_ShowUnitySplashScreen: 1 m_ShowUnitySplashLogo: 1 m_SplashScreenOverlayOpacity: 1 m_SplashScreenAnimation: 1 m_SplashScreenLogoStyle: 1 m_SplashScreenDrawMode: 0 m_SplashScreenBackgroundAnimationZoom: 1 m_SplashScreenLogoAnimationZoom: 1 m_SplashScreenBackgroundLandscapeAspect: 1 m_SplashScreenBackgroundPortraitAspect: 1 m_SplashScreenBackgroundLandscapeUvs: serializedVersion: 2 x: 0 y: 0 width: 1 height: 1 m_SplashScreenBackgroundPortraitUvs: serializedVersion: 2 x: 0 y: 0 width: 1 height: 1 m_SplashScreenLogos: [] m_VirtualRealitySplashScreen: {fileID: 0} m_HolographicTrackingLossScreen: {fileID: 0} defaultScreenWidth: 1024 defaultScreenHeight: 768 defaultScreenWidthWeb: 960 defaultScreenHeightWeb: 600 m_StereoRenderingPath: 0 m_ActiveColorSpace: 1 unsupportedMSAAFallback: 0 m_SpriteBatchMaxVertexCount: 65535 m_SpriteBatchVertexThreshold: 300 m_MTRendering: 1 mipStripping: 0 numberOfMipsStripped: 0 numberOfMipsStrippedPerMipmapLimitGroup: {} m_StackTraceTypes: 010000000100000001000000010000000100000001000000 iosShowActivityIndicatorOnLoading: -1 androidShowActivityIndicatorOnLoading: -1 iosUseCustomAppBackgroundBehavior: 0 allowedAutorotateToPortrait: 1 allowedAutorotateToPortraitUpsideDown: 1 allowedAutorotateToLandscapeRight: 1 allowedAutorotateToLandscapeLeft: 1 useOSAutorotation: 1 use32BitDisplayBuffer: 1 preserveFramebufferAlpha: 0 disableDepthAndStencilBuffers: 0 androidStartInFullscreen: 1 androidRenderOutsideSafeArea: 0 androidUseSwappy: 0 androidBlitType: 0 androidResizeableActivity: 0 androidDefaultWindowWidth: 1920 androidDefaultWindowHeight: 1080 androidMinimumWindowWidth: 400 androidMinimumWindowHeight: 300 androidFullscreenMode: 1 androidAutoRotationBehavior: 1 androidPredictiveBackSupport: 0 androidApplicationEntry: 1 defaultIsNativeResolution: 1 macRetinaSupport: 1 runInBackground: 1 muteOtherAudioSources: 0 Prepare IOS For Recording: 0 Force IOS Speakers When Recording: 0 deferSystemGesturesMode: 0 hideHomeButton: 0 submitAnalytics: 1 usePlayerLog: 1 dedicatedServerOptimizations: 0 bakeCollisionMeshes: 0 forceSingleInstance: 0 useFlipModelSwapchain: 1 resizableWindow: 0 useMacAppStoreValidation: 0 macAppStoreCategory: public.app-category.games gpuSkinning: 0 meshDeformation: 0 xboxPIXTextureCapture: 0 xboxEnableAvatar: 0 xboxEnableKinect: 0 xboxEnableKinectAutoTracking: 0 xboxEnableFitness: 0 visibleInBackground: 0 allowFullscreenSwitch: 1 fullscreenMode: 3 xboxSpeechDB: 0 xboxEnableHeadOrientation: 0 xboxEnableGuest: 0 xboxEnablePIXSampling: 0 metalFramebufferOnly: 0 xboxOneResolution: 0 xboxOneSResolution: 0 xboxOneXResolution: 3 xboxOneMonoLoggingLevel: 0 xboxOneLoggingLevel: 1 xboxOneDisableEsram: 0 xboxOneEnableTypeOptimization: 0 xboxOnePresentImmediateThreshold: 0 switchQueueCommandMemory: 0 switchQueueControlMemory: 16384 switchQueueComputeMemory: 262144 switchNVNShaderPoolsGranularity: 33554432 switchNVNDefaultPoolsGranularity: 16777216 switchNVNOtherPoolsGranularity: 16777216 switchGpuScratchPoolGranularity: 2097152 switchAllowGpuScratchShrinking: 0 switchNVNMaxPublicTextureIDCount: 0 switchNVNMaxPublicSamplerIDCount: 0 switchMaxWorkerMultiple: 8 switchNVNGraphicsFirmwareMemory: 32 vulkanNumSwapchainBuffers: 3 vulkanEnableSetSRGBWrite: 0 vulkanEnablePreTransform: 0 vulkanEnableLateAcquireNextImage: 0 vulkanEnableCommandBufferRecycling: 1 loadStoreDebugModeEnabled: 0 visionOSBundleVersion: 1.0 tvOSBundleVersion: 1.0 bundleVersion: 1.0 preloadedAssets: [] metroInputSource: 0 wsaTransparentSwapchain: 0 m_HolographicPauseOnTrackingLoss: 1 xboxOneDisableKinectGpuReservation: 0 xboxOneEnable7thCore: 0 vrSettings: enable360StereoCapture: 0 isWsaHolographicRemotingEnabled: 0 enableFrameTimingStats: 0 enableOpenGLProfilerGPURecorders: 1 allowHDRDisplaySupport: 0 useHDRDisplay: 0 hdrBitDepth: 0 m_ColorGamuts: 00000000 targetPixelDensity: 30 resolutionScalingMode: 0 resetResolutionOnWindowResize: 0 androidSupportedAspectRatio: 1 androidMaxAspectRatio: 2.1 androidMinAspectRatio: 1 applicationIdentifier: Android: com.test.ET Standalone: com.test.ET Tizen: com.Company.ProductName iPhone: com.test.ET tvOS: com.Company.ProductName buildNumber: Standalone: 0 VisionOS: 0 iPhone: 0 tvOS: 0 overrideDefaultApplicationIdentifier: 0 AndroidBundleVersionCode: 1 AndroidMinSdkVersion: 23 AndroidTargetSdkVersion: 0 AndroidPreferredInstallLocation: 1 aotOptions: stripEngineCode: 0 iPhoneStrippingLevel: 0 iPhoneScriptCallOptimization: 0 ForceInternetPermission: 0 ForceSDCardPermission: 0 CreateWallpaper: 0 androidSplitApplicationBinary: 0 keepLoadedShadersAlive: 0 StripUnusedMeshComponents: 0 strictShaderVariantMatching: 0 VertexChannelCompressionMask: 214 iPhoneSdkVersion: 988 iOSSimulatorArchitecture: 0 iOSTargetOSVersionString: 13.0 tvOSSdkVersion: 0 tvOSSimulatorArchitecture: 0 tvOSRequireExtendedGameController: 0 tvOSTargetOSVersionString: 13.0 VisionOSSdkVersion: 0 VisionOSTargetOSVersionString: 1.0 uIPrerenderedIcon: 0 uIRequiresPersistentWiFi: 0 uIRequiresFullScreen: 1 uIStatusBarHidden: 1 uIExitOnSuspend: 0 uIStatusBarStyle: 0 appleTVSplashScreen: {fileID: 0} appleTVSplashScreen2x: {fileID: 0} tvOSSmallIconLayers: [] tvOSSmallIconLayers2x: [] tvOSLargeIconLayers: [] tvOSLargeIconLayers2x: [] tvOSTopShelfImageLayers: [] tvOSTopShelfImageLayers2x: [] tvOSTopShelfImageWideLayers: [] tvOSTopShelfImageWideLayers2x: [] iOSLaunchScreenType: 0 iOSLaunchScreenPortrait: {fileID: 0} iOSLaunchScreenLandscape: {fileID: 0} iOSLaunchScreenBackgroundColor: serializedVersion: 2 rgba: 0 iOSLaunchScreenFillPct: 100 iOSLaunchScreenSize: 100 iOSLaunchScreeniPadType: 0 iOSLaunchScreeniPadImage: {fileID: 0} iOSLaunchScreeniPadBackgroundColor: serializedVersion: 2 rgba: 0 iOSLaunchScreeniPadFillPct: 100 iOSLaunchScreeniPadSize: 100 iOSLaunchScreenCustomStoryboardPath: iOSLaunchScreeniPadCustomStoryboardPath: iOSDeviceRequirements: [] iOSURLSchemes: [] macOSURLSchemes: [] iOSBackgroundModes: 0 iOSMetalForceHardShadows: 0 metalEditorSupport: 1 metalAPIValidation: 1 metalCompileShaderBinary: 0 iOSRenderExtraFrameOnPause: 1 iosCopyPluginsCodeInsteadOfSymlink: 0 appleDeveloperTeamID: iOSManualSigningProvisioningProfileID: tvOSManualSigningProvisioningProfileID: VisionOSManualSigningProvisioningProfileID: iOSManualSigningProvisioningProfileType: 0 tvOSManualSigningProvisioningProfileType: 0 VisionOSManualSigningProvisioningProfileType: 0 appleEnableAutomaticSigning: 0 iOSRequireARKit: 0 iOSAutomaticallyDetectAndAddCapabilities: 1 appleEnableProMotion: 0 shaderPrecisionModel: 0 clonedFromGUID: 00000000000000000000000000000000 templatePackageId: templateDefaultScene: useCustomMainManifest: 0 useCustomLauncherManifest: 0 useCustomMainGradleTemplate: 0 useCustomLauncherGradleManifest: 0 useCustomBaseGradleTemplate: 0 useCustomGradlePropertiesTemplate: 0 useCustomGradleSettingsTemplate: 0 useCustomProguardFile: 0 AndroidTargetArchitectures: 2 AndroidSplashScreenScale: 0 androidSplashScreen: {fileID: 0} AndroidKeystoreName: AndroidKeyaliasName: AndroidEnableArmv9SecurityFeatures: 0 AndroidEnableArm64MTE: 0 AndroidBuildApkPerCpuArchitecture: 0 AndroidTVCompatibility: 1 AndroidIsGame: 1 AndroidEnableTango: 0 androidEnableBanner: 1 androidUseLowAccuracyLocation: 0 androidUseCustomKeystore: 0 m_AndroidBanners: - width: 320 height: 180 banner: {fileID: 0} androidGamepadSupportLevel: 0 AndroidMinifyRelease: 0 AndroidMinifyDebug: 0 AndroidValidateAppBundleSize: 1 AndroidAppBundleSizeToValidate: 150 AndroidReportGooglePlayAppDependencies: 1 androidSymbolsSizeThreshold: 800 m_BuildTargetIcons: - m_BuildTarget: m_Icons: - serializedVersion: 2 m_Icon: {fileID: 0} m_Width: 128 m_Height: 128 m_Kind: 0 m_BuildTargetPlatformIcons: - m_BuildTarget: Android m_Icons: - m_Textures: [] m_Width: 432 m_Height: 432 m_Kind: 2 m_SubKind: - m_Textures: [] m_Width: 324 m_Height: 324 m_Kind: 2 m_SubKind: - m_Textures: [] m_Width: 216 m_Height: 216 m_Kind: 2 m_SubKind: - m_Textures: [] m_Width: 162 m_Height: 162 m_Kind: 2 m_SubKind: - m_Textures: [] m_Width: 108 m_Height: 108 m_Kind: 2 m_SubKind: - m_Textures: [] m_Width: 81 m_Height: 81 m_Kind: 2 m_SubKind: - m_Textures: [] m_Width: 192 m_Height: 192 m_Kind: 0 m_SubKind: - m_Textures: [] m_Width: 144 m_Height: 144 m_Kind: 0 m_SubKind: - m_Textures: [] m_Width: 96 m_Height: 96 m_Kind: 0 m_SubKind: - m_Textures: [] m_Width: 72 m_Height: 72 m_Kind: 0 m_SubKind: - m_Textures: [] m_Width: 48 m_Height: 48 m_Kind: 0 m_SubKind: - m_Textures: [] m_Width: 36 m_Height: 36 m_Kind: 0 m_SubKind: - m_Textures: [] m_Width: 192 m_Height: 192 m_Kind: 1 m_SubKind: - m_Textures: [] m_Width: 144 m_Height: 144 m_Kind: 1 m_SubKind: - m_Textures: [] m_Width: 96 m_Height: 96 m_Kind: 1 m_SubKind: - m_Textures: [] m_Width: 72 m_Height: 72 m_Kind: 1 m_SubKind: - m_Textures: [] m_Width: 48 m_Height: 48 m_Kind: 1 m_SubKind: - m_Textures: [] m_Width: 36 m_Height: 36 m_Kind: 1 m_SubKind: - m_BuildTarget: iPhone m_Icons: - m_Textures: [] m_Width: 180 m_Height: 180 m_Kind: 0 m_SubKind: iPhone - m_Textures: [] m_Width: 120 m_Height: 120 m_Kind: 0 m_SubKind: iPhone - m_Textures: [] m_Width: 167 m_Height: 167 m_Kind: 0 m_SubKind: iPad - m_Textures: [] m_Width: 152 m_Height: 152 m_Kind: 0 m_SubKind: iPad - m_Textures: [] m_Width: 76 m_Height: 76 m_Kind: 0 m_SubKind: iPad - m_Textures: [] m_Width: 120 m_Height: 120 m_Kind: 3 m_SubKind: iPhone - m_Textures: [] m_Width: 80 m_Height: 80 m_Kind: 3 m_SubKind: iPhone - m_Textures: [] m_Width: 80 m_Height: 80 m_Kind: 3 m_SubKind: iPad - m_Textures: [] m_Width: 40 m_Height: 40 m_Kind: 3 m_SubKind: iPad - m_Textures: [] m_Width: 87 m_Height: 87 m_Kind: 1 m_SubKind: iPhone - m_Textures: [] m_Width: 58 m_Height: 58 m_Kind: 1 m_SubKind: iPhone - m_Textures: [] m_Width: 29 m_Height: 29 m_Kind: 1 m_SubKind: iPhone - m_Textures: [] m_Width: 58 m_Height: 58 m_Kind: 1 m_SubKind: iPad - m_Textures: [] m_Width: 29 m_Height: 29 m_Kind: 1 m_SubKind: iPad - m_Textures: [] m_Width: 60 m_Height: 60 m_Kind: 2 m_SubKind: iPhone - m_Textures: [] m_Width: 40 m_Height: 40 m_Kind: 2 m_SubKind: iPhone - m_Textures: [] m_Width: 40 m_Height: 40 m_Kind: 2 m_SubKind: iPad - m_Textures: [] m_Width: 20 m_Height: 20 m_Kind: 2 m_SubKind: iPad - m_Textures: [] m_Width: 1024 m_Height: 1024 m_Kind: 4 m_SubKind: App Store m_BuildTargetBatching: [] m_BuildTargetShaderSettings: [] m_BuildTargetGraphicsJobs: - m_BuildTarget: MacStandaloneSupport m_GraphicsJobs: 0 - m_BuildTarget: Switch m_GraphicsJobs: 0 - m_BuildTarget: MetroSupport m_GraphicsJobs: 0 - m_BuildTarget: AppleTVSupport m_GraphicsJobs: 0 - m_BuildTarget: BJMSupport m_GraphicsJobs: 0 - m_BuildTarget: LinuxStandaloneSupport m_GraphicsJobs: 0 - m_BuildTarget: PS4Player m_GraphicsJobs: 0 - m_BuildTarget: iOSSupport m_GraphicsJobs: 0 - m_BuildTarget: WindowsStandaloneSupport m_GraphicsJobs: 0 - m_BuildTarget: XboxOnePlayer m_GraphicsJobs: 0 - m_BuildTarget: LuminSupport m_GraphicsJobs: 0 - m_BuildTarget: CloudRendering m_GraphicsJobs: 0 - m_BuildTarget: AndroidPlayer m_GraphicsJobs: 0 - m_BuildTarget: WebGLSupport m_GraphicsJobs: 0 m_BuildTargetGraphicsJobMode: - m_BuildTarget: PS4Player m_GraphicsJobMode: 0 - m_BuildTarget: XboxOnePlayer m_GraphicsJobMode: 0 m_BuildTargetGraphicsAPIs: - m_BuildTarget: AndroidPlayer m_APIs: 0b000000 m_Automatic: 0 - m_BuildTarget: iOSSupport m_APIs: 10000000 m_Automatic: 1 m_BuildTargetVRSettings: - m_BuildTarget: Android m_Enabled: 0 m_Devices: - Oculus - m_BuildTarget: Windows Store Apps m_Enabled: 0 m_Devices: [] - m_BuildTarget: N3DS m_Enabled: 0 m_Devices: [] - m_BuildTarget: PS3 m_Enabled: 0 m_Devices: [] - m_BuildTarget: PS4 m_Enabled: 0 m_Devices: - PlayStationVR - m_BuildTarget: PSM m_Enabled: 0 m_Devices: [] - m_BuildTarget: PSP2 m_Enabled: 0 m_Devices: [] - m_BuildTarget: SamsungTV m_Enabled: 0 m_Devices: [] - m_BuildTarget: Standalone m_Enabled: 0 m_Devices: - Oculus - m_BuildTarget: Tizen m_Enabled: 0 m_Devices: [] - m_BuildTarget: WebGL m_Enabled: 0 m_Devices: [] - m_BuildTarget: WebPlayer m_Enabled: 0 m_Devices: [] - m_BuildTarget: WiiU m_Enabled: 0 m_Devices: [] - m_BuildTarget: Xbox360 m_Enabled: 0 m_Devices: [] - m_BuildTarget: XboxOne m_Enabled: 0 m_Devices: [] - m_BuildTarget: iPhone m_Enabled: 0 m_Devices: [] - m_BuildTarget: tvOS m_Enabled: 0 m_Devices: [] m_DefaultShaderChunkSizeInMB: 16 m_DefaultShaderChunkCount: 0 openGLRequireES31: 1 openGLRequireES31AEP: 0 openGLRequireES32: 0 m_TemplateCustomTags: {} mobileMTRendering: Android: 1 iPhone: 1 tvOS: 1 m_BuildTargetGroupLightmapEncodingQuality: - serializedVersion: 2 m_BuildTarget: Standalone m_EncodingQuality: 1 - serializedVersion: 2 m_BuildTarget: XboxOne m_EncodingQuality: 1 - serializedVersion: 2 m_BuildTarget: PS4 m_EncodingQuality: 1 m_BuildTargetGroupLightmapSettings: [] m_BuildTargetGroupLoadStoreDebugModeSettings: [] m_BuildTargetNormalMapEncoding: [] m_BuildTargetDefaultTextureCompressionFormat: [] playModeTestRunnerEnabled: 0 runPlayModeTestAsEditModeTest: 0 actionOnDotNetUnhandledException: 1 editorGfxJobOverride: 1 enableInternalProfiler: 0 logObjCUncaughtExceptions: 1 enableCrashReportAPI: 0 cameraUsageDescription: locationUsageDescription: microphoneUsageDescription: bluetoothUsageDescription: macOSTargetOSVersion: 11.0 switchNMETAOverride: switchNetLibKey: switchSocketMemoryPoolSize: 6144 switchSocketAllocatorPoolSize: 128 switchSocketConcurrencyLimit: 14 switchScreenResolutionBehavior: 2 switchUseCPUProfiler: 0 switchEnableFileSystemTrace: 0 switchLTOSetting: 0 switchApplicationID: 0x01004b9000490000 switchNSODependencies: switchCompilerFlags: switchTitleNames_0: switchTitleNames_1: switchTitleNames_2: switchTitleNames_3: switchTitleNames_4: switchTitleNames_5: switchTitleNames_6: switchTitleNames_7: switchTitleNames_8: switchTitleNames_9: switchTitleNames_10: switchTitleNames_11: switchTitleNames_12: switchTitleNames_13: switchTitleNames_14: switchTitleNames_15: switchPublisherNames_0: switchPublisherNames_1: switchPublisherNames_2: switchPublisherNames_3: switchPublisherNames_4: switchPublisherNames_5: switchPublisherNames_6: switchPublisherNames_7: switchPublisherNames_8: switchPublisherNames_9: switchPublisherNames_10: switchPublisherNames_11: switchPublisherNames_12: switchPublisherNames_13: switchPublisherNames_14: switchPublisherNames_15: switchIcons_0: {fileID: 0} switchIcons_1: {fileID: 0} switchIcons_2: {fileID: 0} switchIcons_3: {fileID: 0} switchIcons_4: {fileID: 0} switchIcons_5: {fileID: 0} switchIcons_6: {fileID: 0} switchIcons_7: {fileID: 0} switchIcons_8: {fileID: 0} switchIcons_9: {fileID: 0} switchIcons_10: {fileID: 0} switchIcons_11: {fileID: 0} switchIcons_12: {fileID: 0} switchIcons_13: {fileID: 0} switchIcons_14: {fileID: 0} switchIcons_15: {fileID: 0} switchSmallIcons_0: {fileID: 0} switchSmallIcons_1: {fileID: 0} switchSmallIcons_2: {fileID: 0} switchSmallIcons_3: {fileID: 0} switchSmallIcons_4: {fileID: 0} switchSmallIcons_5: {fileID: 0} switchSmallIcons_6: {fileID: 0} switchSmallIcons_7: {fileID: 0} switchSmallIcons_8: {fileID: 0} switchSmallIcons_9: {fileID: 0} switchSmallIcons_10: {fileID: 0} switchSmallIcons_11: {fileID: 0} switchSmallIcons_12: {fileID: 0} switchSmallIcons_13: {fileID: 0} switchSmallIcons_14: {fileID: 0} switchSmallIcons_15: {fileID: 0} switchManualHTML: switchAccessibleURLs: switchLegalInformation: switchMainThreadStackSize: 1048576 switchPresenceGroupId: 0x01004b9000490000 switchLogoHandling: 0 switchReleaseVersion: 0 switchDisplayVersion: 1.0.0 switchStartupUserAccount: 0 switchSupportedLanguagesMask: 0 switchLogoType: 0 switchApplicationErrorCodeCategory: switchUserAccountSaveDataSize: 0 switchUserAccountSaveDataJournalSize: 0 switchApplicationAttribute: 0 switchCardSpecSize: 4 switchCardSpecClock: 25 switchRatingsMask: 0 switchRatingsInt_0: 0 switchRatingsInt_1: 0 switchRatingsInt_2: 0 switchRatingsInt_3: 0 switchRatingsInt_4: 0 switchRatingsInt_5: 0 switchRatingsInt_6: 0 switchRatingsInt_7: 0 switchRatingsInt_8: 0 switchRatingsInt_9: 0 switchRatingsInt_10: 0 switchRatingsInt_11: 0 switchRatingsInt_12: 0 switchLocalCommunicationIds_0: 0x01004b9000490000 switchLocalCommunicationIds_1: switchLocalCommunicationIds_2: switchLocalCommunicationIds_3: switchLocalCommunicationIds_4: switchLocalCommunicationIds_5: switchLocalCommunicationIds_6: switchLocalCommunicationIds_7: switchParentalControl: 0 switchAllowsScreenshot: 1 switchAllowsVideoCapturing: 1 switchAllowsRuntimeAddOnContentInstall: 0 switchDataLossConfirmation: 0 switchUserAccountLockEnabled: 0 switchSystemResourceMemory: 16777216 switchSupportedNpadStyles: 3 switchNativeFsCacheSize: 32 switchIsHoldTypeHorizontal: 0 switchSupportedNpadCount: 8 switchEnableTouchScreen: 1 switchSocketConfigEnabled: 0 switchTcpInitialSendBufferSize: 32 switchTcpInitialReceiveBufferSize: 64 switchTcpAutoSendBufferSizeMax: 256 switchTcpAutoReceiveBufferSizeMax: 256 switchUdpSendBufferSize: 9 switchUdpReceiveBufferSize: 42 switchSocketBufferEfficiency: 4 switchSocketInitializeEnabled: 1 switchNetworkInterfaceManagerInitializeEnabled: 1 switchDisableHTCSPlayerConnection: 0 switchUseNewStyleFilepaths: 0 switchUseLegacyFmodPriorities: 1 switchUseMicroSleepForYield: 1 switchEnableRamDiskSupport: 0 switchMicroSleepForYieldTime: 25 switchRamDiskSpaceSize: 12 switchUpgradedPlayerSettingsToNMETA: 0 ps4NPAgeRating: 12 ps4NPTitleSecret: ps4NPTrophyPackPath: ps4ParentalLevel: 1 ps4ContentID: ED1633-NPXX51362_00-0000000000000000 ps4Category: 0 ps4MasterVersion: 01.00 ps4AppVersion: 01.00 ps4AppType: 0 ps4ParamSfxPath: ps4VideoOutPixelFormat: 0 ps4VideoOutInitialWidth: 1920 ps4VideoOutBaseModeInitialWidth: 1920 ps4VideoOutReprojectionRate: 120 ps4PronunciationXMLPath: ps4PronunciationSIGPath: ps4BackgroundImagePath: ps4StartupImagePath: ps4StartupImagesFolder: ps4IconImagesFolder: ps4SaveDataImagePath: ps4SdkOverride: ps4BGMPath: ps4ShareFilePath: ps4ShareOverlayImagePath: ps4PrivacyGuardImagePath: ps4ExtraSceSysFile: ps4NPtitleDatPath: ps4RemotePlayKeyAssignment: -1 ps4RemotePlayKeyMappingDir: ps4PlayTogetherPlayerCount: 0 ps4EnterButtonAssignment: 1 ps4ApplicationParam1: 0 ps4ApplicationParam2: 0 ps4ApplicationParam3: 0 ps4ApplicationParam4: 0 ps4DownloadDataSize: 0 ps4GarlicHeapSize: 2048 ps4ProGarlicHeapSize: 2560 playerPrefsMaxSize: 32768 ps4Passcode: eaoEiIgxIX4a2dREbbSqWy6yhKIDCdJO ps4pnSessions: 1 ps4pnPresence: 1 ps4pnFriends: 1 ps4pnGameCustomData: 1 playerPrefsSupport: 0 enableApplicationExit: 0 resetTempFolder: 1 restrictedAudioUsageRights: 0 ps4UseResolutionFallback: 0 ps4ReprojectionSupport: 0 ps4UseAudio3dBackend: 0 ps4UseLowGarlicFragmentationMode: 1 ps4SocialScreenEnabled: 0 ps4ScriptOptimizationLevel: 3 ps4Audio3dVirtualSpeakerCount: 14 ps4attribCpuUsage: 0 ps4PatchPkgPath: ps4PatchLatestPkgPath: ps4PatchChangeinfoPath: ps4PatchDayOne: 0 ps4attribUserManagement: 0 ps4attribMoveSupport: 0 ps4attrib3DSupport: 0 ps4attribShareSupport: 0 ps4attribExclusiveVR: 0 ps4disableAutoHideSplash: 0 ps4videoRecordingFeaturesUsed: 0 ps4contentSearchFeaturesUsed: 0 ps4CompatibilityPS5: 0 ps4AllowPS5Detection: 0 ps4GPU800MHz: 1 ps4attribEyeToEyeDistanceSettingVR: 0 ps4IncludedModules: [] ps4attribVROutputEnabled: 0 monoEnv: splashScreenBackgroundSourceLandscape: {fileID: 0} splashScreenBackgroundSourcePortrait: {fileID: 0} blurSplashScreenBackground: 1 spritePackerPolicy: webGLMemorySize: 256 webGLExceptionSupport: 1 webGLNameFilesAsHashes: 0 webGLShowDiagnostics: 0 webGLDataCaching: 0 webGLDebugSymbols: 0 webGLEmscriptenArgs: webGLModulesDirectory: webGLTemplate: APPLICATION:Default webGLAnalyzeBuildSize: 0 webGLUseEmbeddedResources: 0 webGLCompressionFormat: 1 webGLWasmArithmeticExceptions: 0 webGLLinkerTarget: 1 webGLThreadsSupport: 0 webGLDecompressionFallback: 0 webGLInitialMemorySize: 32 webGLMaximumMemorySize: 2048 webGLMemoryGrowthMode: 2 webGLMemoryLinearGrowthStep: 16 webGLMemoryGeometricGrowthStep: 0.2 webGLMemoryGeometricGrowthCap: 96 webGLEnableWebGPU: 0 webGLPowerPreference: 2 webGLWebAssemblyTable: 0 webGLWebAssemblyBigInt: 0 webGLCloseOnQuit: 0 webWasm2023: 0 scriptingDefineSymbols: Android: UNITY;ENABLE_VIEW Server: UNITY;ENABLE_VIEW Standalone: UNITY;ENABLE_VIEW WebGL: UNITY;ENABLE_VIEW iPhone: UNITY;ENABLE_VIEW additionalCompilerArguments: {} platformArchitecture: iPhone: 1 scriptingBackend: Android: 1 Server: 0 Standalone: 1 iPhone: 1 il2cppCompilerConfiguration: {} il2cppCodeGeneration: {} il2cppStacktraceInformation: {} managedStrippingLevel: Android: 1 EmbeddedLinux: 1 GameCoreScarlett: 1 GameCoreXboxOne: 1 Lumin: 1 Nintendo Switch: 1 PS4: 1 PS5: 1 Stadia: 1 Standalone: 1 WebGL: 1 Windows Store Apps: 1 XboxOne: 1 iPhone: 1 tvOS: 1 incrementalIl2cppBuild: {} suppressCommonWarnings: 1 allowUnsafeCode: 1 useDeterministicCompilation: 1 additionalIl2CppArgs: scriptingRuntimeVersion: 1 gcIncremental: 0 gcWBarrierValidation: 0 apiCompatibilityLevelPerPlatform: Android: 3 Server: 3 Standalone: 3 editorAssembliesCompatibilityLevel: 2 m_RenderingPath: 1 m_MobileRenderingPath: 1 metroPackageName: Unity metroPackageVersion: metroCertificatePath: metroCertificatePassword: metroCertificateSubject: metroCertificateIssuer: metroCertificateNotAfter: 0000000000000000 metroApplicationDescription: Unity wsaImages: {} metroTileShortName: metroTileShowName: 0 metroMediumTileShowName: 0 metroLargeTileShowName: 0 metroWideTileShowName: 0 metroSupportStreamingInstall: 0 metroLastRequiredScene: 0 metroDefaultTileSize: 1 metroTileForegroundText: 1 metroTileBackgroundColor: {r: 0, g: 0, b: 0, a: 1} metroSplashScreenBackgroundColor: {r: 0, g: 0, b: 0, a: 1} metroSplashScreenUseBackgroundColor: 0 syncCapabilities: 0 platformCapabilities: {} metroTargetDeviceFamilies: {} metroFTAName: metroFTAFileTypes: [] metroProtocolName: vcxProjDefaultLanguage: XboxOneProductId: XboxOneUpdateKey: XboxOneSandboxId: XboxOneContentId: XboxOneTitleId: XboxOneSCId: XboxOneGameOsOverridePath: XboxOnePackagingOverridePath: XboxOneAppManifestOverridePath: XboxOneVersion: 1.0.0.0 XboxOnePackageEncryption: 0 XboxOnePackageUpdateGranularity: 2 XboxOneDescription: XboxOneLanguage: - enus XboxOneCapability: [] XboxOneGameRating: {} XboxOneIsContentPackage: 0 XboxOneEnhancedXboxCompatibilityMode: 0 XboxOneEnableGPUVariability: 0 XboxOneSockets: {} XboxOneSplashScreen: {fileID: 0} XboxOneAllowedProductIds: [] XboxOnePersistentLocalStorageSize: 0 XboxOneXTitleMemory: 8 XboxOneOverrideIdentityName: XboxOneOverrideIdentityPublisher: vrEditorSettings: {} cloudServicesEnabled: Analytics: 0 Build: 0 Collab: 0 ErrorHub: 0 Game_Performance: 0 Hub: 0 Purchasing: 0 UNet: 0 Unity_Ads: 0 luminIcon: m_Name: m_ModelFolderPath: m_PortalFolderPath: luminCert: m_CertPath: m_SignPackage: 1 luminIsChannelApp: 0 luminVersion: m_VersionCode: 1 m_VersionName: hmiPlayerDataPath: hmiForceSRGBBlit: 1 embeddedLinuxEnableGamepadInput: 1 hmiCpuConfiguration: hmiLogStartupTiming: 0 qnxGraphicConfPath: apiCompatibilityLevel: 3 captureStartupLogs: {} activeInputHandler: 0 windowsGamepadBackendHint: 0 cloudProjectId: framebufferDepthMemorylessMode: 0 qualitySettingsNames: [] projectName: organizationId: cloudEnabled: 0 legacyClampBlendShapeWeights: 0 hmiLoadingImage: {fileID: 0} platformRequiresReadableAssets: 0 virtualTexturingSupportEnabled: 0 insecureHttpOption: 0 androidVulkanDenyFilterList: [] androidVulkanAllowFilterList: [] ================================================ FILE: ProjectSettings/ProjectVersion.txt ================================================ m_EditorVersion: 6000.0.25f1 m_EditorVersionWithRevision: 6000.0.25f1 (4859ab7b5a49) ================================================ FILE: ProjectSettings/QualitySettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!47 &1 QualitySettings: m_ObjectHideFlags: 0 serializedVersion: 5 m_CurrentQuality: 0 m_QualitySettings: - serializedVersion: 4 name: Fastest pixelLightCount: 0 shadows: 0 shadowResolution: 0 shadowProjection: 1 shadowCascades: 1 shadowDistance: 15 shadowNearPlaneOffset: 2 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 0 skinWeights: 1 globalTextureMipmapLimit: 1 textureMipmapLimitSettings: [] anisotropicTextures: 0 antiAliasing: 0 softParticles: 0 softVegetation: 0 realtimeReflectionProbes: 0 billboardsFaceCameraPosition: 0 useLegacyDetailDistribution: 1 adaptiveVsync: 0 vSyncCount: 0 realtimeGICPUUsage: 25 adaptiveVsyncExtraA: 0 adaptiveVsyncExtraB: 0 lodBias: 0.3 maximumLODLevel: 0 enableLODCrossFade: 1 streamingMipmapsActive: 0 streamingMipmapsAddAllCameras: 1 streamingMipmapsMemoryBudget: 512 streamingMipmapsRenderersPerFrame: 512 streamingMipmapsMaxLevelReduction: 2 streamingMipmapsMaxFileIORequests: 1024 particleRaycastBudget: 4 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 4 asyncUploadPersistentBuffer: 1 resolutionScalingFixedDPIFactor: 1 customRenderPipeline: {fileID: 0} terrainQualityOverrides: 0 terrainPixelError: 1 terrainDetailDensityScale: 1 terrainBasemapDistance: 1000 terrainDetailDistance: 80 terrainTreeDistance: 5000 terrainBillboardStart: 50 terrainFadeLength: 5 terrainMaxTrees: 50 excludedTargetPlatforms: [] - serializedVersion: 4 name: Fast pixelLightCount: 0 shadows: 0 shadowResolution: 0 shadowProjection: 1 shadowCascades: 1 shadowDistance: 20 shadowNearPlaneOffset: 2 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 0 skinWeights: 2 globalTextureMipmapLimit: 0 textureMipmapLimitSettings: [] anisotropicTextures: 0 antiAliasing: 0 softParticles: 0 softVegetation: 0 realtimeReflectionProbes: 0 billboardsFaceCameraPosition: 0 useLegacyDetailDistribution: 1 adaptiveVsync: 0 vSyncCount: 0 realtimeGICPUUsage: 25 adaptiveVsyncExtraA: 0 adaptiveVsyncExtraB: 0 lodBias: 0.4 maximumLODLevel: 0 enableLODCrossFade: 1 streamingMipmapsActive: 0 streamingMipmapsAddAllCameras: 1 streamingMipmapsMemoryBudget: 512 streamingMipmapsRenderersPerFrame: 512 streamingMipmapsMaxLevelReduction: 2 streamingMipmapsMaxFileIORequests: 1024 particleRaycastBudget: 16 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 4 asyncUploadPersistentBuffer: 1 resolutionScalingFixedDPIFactor: 1 customRenderPipeline: {fileID: 0} terrainQualityOverrides: 0 terrainPixelError: 1 terrainDetailDensityScale: 1 terrainBasemapDistance: 1000 terrainDetailDistance: 80 terrainTreeDistance: 5000 terrainBillboardStart: 50 terrainFadeLength: 5 terrainMaxTrees: 50 excludedTargetPlatforms: [] - serializedVersion: 4 name: Simple pixelLightCount: 1 shadows: 1 shadowResolution: 0 shadowProjection: 1 shadowCascades: 1 shadowDistance: 20 shadowNearPlaneOffset: 2 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 0 skinWeights: 2 globalTextureMipmapLimit: 0 textureMipmapLimitSettings: [] anisotropicTextures: 1 antiAliasing: 0 softParticles: 0 softVegetation: 0 realtimeReflectionProbes: 0 billboardsFaceCameraPosition: 0 useLegacyDetailDistribution: 1 adaptiveVsync: 0 vSyncCount: 0 realtimeGICPUUsage: 25 adaptiveVsyncExtraA: 0 adaptiveVsyncExtraB: 0 lodBias: 0.7 maximumLODLevel: 0 enableLODCrossFade: 1 streamingMipmapsActive: 0 streamingMipmapsAddAllCameras: 1 streamingMipmapsMemoryBudget: 512 streamingMipmapsRenderersPerFrame: 512 streamingMipmapsMaxLevelReduction: 2 streamingMipmapsMaxFileIORequests: 1024 particleRaycastBudget: 64 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 4 asyncUploadPersistentBuffer: 1 resolutionScalingFixedDPIFactor: 1 customRenderPipeline: {fileID: 0} terrainQualityOverrides: 0 terrainPixelError: 1 terrainDetailDensityScale: 1 terrainBasemapDistance: 1000 terrainDetailDistance: 80 terrainTreeDistance: 5000 terrainBillboardStart: 50 terrainFadeLength: 5 terrainMaxTrees: 50 excludedTargetPlatforms: [] - serializedVersion: 4 name: Good pixelLightCount: 2 shadows: 2 shadowResolution: 1 shadowProjection: 1 shadowCascades: 2 shadowDistance: 40 shadowNearPlaneOffset: 2 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 1 skinWeights: 2 globalTextureMipmapLimit: 0 textureMipmapLimitSettings: [] anisotropicTextures: 1 antiAliasing: 0 softParticles: 0 softVegetation: 1 realtimeReflectionProbes: 1 billboardsFaceCameraPosition: 1 useLegacyDetailDistribution: 1 adaptiveVsync: 0 vSyncCount: 1 realtimeGICPUUsage: 50 adaptiveVsyncExtraA: 0 adaptiveVsyncExtraB: 0 lodBias: 1 maximumLODLevel: 0 enableLODCrossFade: 1 streamingMipmapsActive: 0 streamingMipmapsAddAllCameras: 1 streamingMipmapsMemoryBudget: 512 streamingMipmapsRenderersPerFrame: 512 streamingMipmapsMaxLevelReduction: 2 streamingMipmapsMaxFileIORequests: 1024 particleRaycastBudget: 256 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 4 asyncUploadPersistentBuffer: 1 resolutionScalingFixedDPIFactor: 1 customRenderPipeline: {fileID: 11400000, guid: 313955cd6271f5c4985cba4adc30fc8e, type: 2} terrainQualityOverrides: 0 terrainPixelError: 1 terrainDetailDensityScale: 1 terrainBasemapDistance: 1000 terrainDetailDistance: 80 terrainTreeDistance: 5000 terrainBillboardStart: 50 terrainFadeLength: 5 terrainMaxTrees: 50 excludedTargetPlatforms: [] - serializedVersion: 4 name: Beautiful pixelLightCount: 3 shadows: 2 shadowResolution: 2 shadowProjection: 1 shadowCascades: 2 shadowDistance: 70 shadowNearPlaneOffset: 2 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 1 skinWeights: 4 globalTextureMipmapLimit: 0 textureMipmapLimitSettings: [] anisotropicTextures: 2 antiAliasing: 2 softParticles: 1 softVegetation: 1 realtimeReflectionProbes: 1 billboardsFaceCameraPosition: 1 useLegacyDetailDistribution: 1 adaptiveVsync: 0 vSyncCount: 1 realtimeGICPUUsage: 50 adaptiveVsyncExtraA: 0 adaptiveVsyncExtraB: 0 lodBias: 1.5 maximumLODLevel: 0 enableLODCrossFade: 1 streamingMipmapsActive: 0 streamingMipmapsAddAllCameras: 1 streamingMipmapsMemoryBudget: 512 streamingMipmapsRenderersPerFrame: 512 streamingMipmapsMaxLevelReduction: 2 streamingMipmapsMaxFileIORequests: 1024 particleRaycastBudget: 1024 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 4 asyncUploadPersistentBuffer: 1 resolutionScalingFixedDPIFactor: 1 customRenderPipeline: {fileID: 0} terrainQualityOverrides: 0 terrainPixelError: 1 terrainDetailDensityScale: 1 terrainBasemapDistance: 1000 terrainDetailDistance: 80 terrainTreeDistance: 5000 terrainBillboardStart: 50 terrainFadeLength: 5 terrainMaxTrees: 50 excludedTargetPlatforms: [] - serializedVersion: 4 name: Fantastic pixelLightCount: 4 shadows: 2 shadowResolution: 2 shadowProjection: 1 shadowCascades: 4 shadowDistance: 150 shadowNearPlaneOffset: 2 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 1 skinWeights: 4 globalTextureMipmapLimit: 0 textureMipmapLimitSettings: [] anisotropicTextures: 2 antiAliasing: 2 softParticles: 1 softVegetation: 1 realtimeReflectionProbes: 1 billboardsFaceCameraPosition: 1 useLegacyDetailDistribution: 1 adaptiveVsync: 0 vSyncCount: 1 realtimeGICPUUsage: 100 adaptiveVsyncExtraA: 0 adaptiveVsyncExtraB: 0 lodBias: 2 maximumLODLevel: 0 enableLODCrossFade: 1 streamingMipmapsActive: 0 streamingMipmapsAddAllCameras: 1 streamingMipmapsMemoryBudget: 512 streamingMipmapsRenderersPerFrame: 512 streamingMipmapsMaxLevelReduction: 2 streamingMipmapsMaxFileIORequests: 1024 particleRaycastBudget: 4096 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 4 asyncUploadPersistentBuffer: 1 resolutionScalingFixedDPIFactor: 1 customRenderPipeline: {fileID: 0} terrainQualityOverrides: 0 terrainPixelError: 1 terrainDetailDensityScale: 1 terrainBasemapDistance: 1000 terrainDetailDistance: 80 terrainTreeDistance: 5000 terrainBillboardStart: 50 terrainFadeLength: 5 terrainMaxTrees: 50 excludedTargetPlatforms: [] m_TextureMipmapLimitGroupNames: [] m_PerPlatformDefaultQuality: {} ================================================ FILE: ProjectSettings/SceneTemplateSettings.json ================================================ { "templatePinStates": [], "dependencyTypeInfos": [ { "userAdded": false, "type": "UnityEngine.AnimationClip", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEditor.Animations.AnimatorController", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.AnimatorOverrideController", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEditor.Audio.AudioMixerController", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.ComputeShader", "defaultInstantiationMode": 1 }, { "userAdded": false, "type": "UnityEngine.Cubemap", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.GameObject", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEditor.LightingDataAsset", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.LightingSettings", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Material", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEditor.MonoScript", "defaultInstantiationMode": 1 }, { "userAdded": false, "type": "UnityEngine.PhysicMaterial", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.PhysicsMaterial", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.PhysicsMaterial2D", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Rendering.PostProcessing.PostProcessProfile", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Rendering.PostProcessing.PostProcessResources", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Rendering.VolumeProfile", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEditor.SceneAsset", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Shader", "defaultInstantiationMode": 1 }, { "userAdded": false, "type": "UnityEngine.ShaderVariantCollection", "defaultInstantiationMode": 1 }, { "userAdded": false, "type": "UnityEngine.Texture", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Texture2D", "defaultInstantiationMode": 0 }, { "userAdded": false, "type": "UnityEngine.Timeline.TimelineAsset", "defaultInstantiationMode": 0 } ], "defaultDependencyTypeInfo": { "userAdded": false, "type": "", "defaultInstantiationMode": 1 }, "newSceneOverride": 0 } ================================================ FILE: ProjectSettings/ShaderGraphSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!114 &1 MonoBehaviour: m_ObjectHideFlags: 61 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: de02f9e1d18f588468e474319d09a723, type: 3} m_Name: m_EditorClassIdentifier: shaderVariantLimit: 128 customInterpolatorErrorThreshold: 32 customInterpolatorWarningThreshold: 16 customHeatmapValues: {fileID: 0} ================================================ FILE: ProjectSettings/TagManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!78 &1 TagManager: serializedVersion: 3 tags: [] layers: - Default - TransparentFX - Ignore Raycast - - Water - UI - - - Map - Hidden - - - - - - - - - - - - - - - - - - - - - - m_SortingLayers: - name: Default uniqueID: 0 locked: 0 m_RenderingLayers: - Default - Light Layer 1 - Light Layer 2 - Light Layer 3 - Light Layer 4 - Light Layer 5 - Light Layer 6 - Light Layer 7 ================================================ FILE: ProjectSettings/TimeManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!5 &1 TimeManager: m_ObjectHideFlags: 0 Fixed Timestep: 0.02 Maximum Allowed Timestep: 0.33333334 m_TimeScale: 1 ================================================ FILE: ProjectSettings/TimelineSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!114 &1 MonoBehaviour: m_ObjectHideFlags: 61 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: a287be6c49135cd4f9b2b8666c39d999, type: 3} m_Name: m_EditorClassIdentifier: assetDefaultFramerate: 60 m_DefaultFrameRate: 60 ================================================ FILE: ProjectSettings/URPProjectSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!114 &1 MonoBehaviour: m_ObjectHideFlags: 61 m_CorrespondingSourceObject: {fileID: 0} m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 0} m_Enabled: 1 m_EditorHideFlags: 0 m_Script: {fileID: 11500000, guid: 247994e1f5a72c2419c26a37e9334c01, type: 3} m_Name: m_EditorClassIdentifier: m_LastMaterialVersion: 9 ================================================ FILE: ProjectSettings/UnityConnectSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!310 &1 UnityConnectSettings: m_ObjectHideFlags: 0 serializedVersion: 1 m_Enabled: 0 m_TestMode: 0 m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events m_EventUrl: https://cdp.cloud.unity3d.com/v1/events m_ConfigUrl: https://config.uca.cloud.unity3d.com m_DashboardUrl: https://dashboard.unity3d.com m_CNEventUrl: https://cdp.cloud.unity.cn/v1/events m_CNConfigUrl: https://cdp.cloud.unity.cn/config m_TestInitMode: 0 CrashReportingSettings: m_EventUrl: https://perf-events.cloud.unity3d.com m_Enabled: 0 m_LogBufferSize: 10 m_CaptureEditorExceptions: 1 UnityPurchasingSettings: m_Enabled: 0 m_TestMode: 0 UnityAnalyticsSettings: m_Enabled: 0 m_TestMode: 0 m_InitializeOnStartup: 1 UnityAdsSettings: m_Enabled: 0 m_InitializeOnStartup: 1 m_TestMode: 0 m_IosGameId: m_AndroidGameId: m_GameIds: {} m_GameId: PerformanceReportingSettings: m_Enabled: 0 ================================================ FILE: ProjectSettings/VFXManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!937362698 &1 VFXManager: m_ObjectHideFlags: 0 m_IndirectShader: {fileID: 0} m_CopyBufferShader: {fileID: 0} m_SortShader: {fileID: 0} m_RenderPipeSettingsPath: m_FixedTimeStep: 0.016666668 m_MaxDeltaTime: 0.05 ================================================ FILE: ProjectSettings/VersionControlSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!890905787 &1 VersionControlSettings: m_ObjectHideFlags: 0 m_Mode: Visible Meta Files m_CollabEditorSettings: inProgressEnabled: 1 ================================================ FILE: ProjectSettings/XRSettings.asset ================================================ { "m_SettingKeys": [ "VR Device Disabled", "VR Device User Alert" ], "m_SettingValues": [ "False", "False" ] } ================================================ FILE: README.md ================================================ # English: please use your browser to translate to english # 全新结合AI的ET框架,实现AI自动写逻辑,自动写机器人测试用例,自动编译,自动运行,全闭环操作 1. 全新的机器人测试框架,完全为AI设计。方便AI自动编写机器人用例,每个机器人用例都是一个全新沙箱环境,相互之间不受影响。配置等等无需重新加载,比每次启动进程速度快的多 2. 全新为AI实现的新的Fiber机制,Fiber有父子关系,Fiber由谁创建则由谁施放,安全可靠 3. 全新的日志设计,消息流更加清晰 4. 为AI设计的各种分析器,严格限制AI,让AI写出的代码可读可改 5. 增加服务发现功能,云原生支持,ip端口自动配置,router,gate等动态伸缩 6. 增加Excel skills方便ai使用 7. 视频演示: [游戏逻辑开发-Claude Code With ET1](https://www.bilibili.com/video/BV1ykujzcEvp/?vd_source=e55f8234b8f8039504cbf131082c93dd#reply268665744592) [游戏逻辑开发-Claude Code With ET2](https://www.bilibili.com/video/BV15juAzrEeg?spm_id_from=333.788.recommend_more_video.0&vd_source=e55f8234b8f8039504cbf131082c93dd) [新的Fiber设计](https://www.bilibili.com/video/BV1KntGzvEho/?vd_source=e55f8234b8f8039504cbf131082c93dd#reply271421069312) # ET类魔兽世界预制游戏与课程上架,已经录完75集 WOW075-云原生03.mp4 WOW074-云原生02.mp4 WOW073-云原生01.mp4 WOW072-新协程锁.mp4 WOW071-相位02.mp4 WOW070-相位01.mp4 WOW069-合线03.mp4 WOW068-合线02.mp4 WOW067-合线01.mp4 WOW066-分线副本02.mp4 WOW065-分线副本01.mp4 WOW064-无缝传送.mp4 WOW063-动态创建地图03.mp4 WOW062-动态创建地图02.mp4 WOW061-动态创建地图01.mp4 WOW060-包分析器.mp4 WOW059-Luban跟日志.mp4 WOW058-新的分析器.mp4 WOW057-机器人单元测试 WOW056-New Fiber.mp4 WOW055-Claude Code With ET2.mp4 WOW054-Claude Code With ET1.mp4 WOW053-ET cursor使用入门.mp4 WOW052-行为树编辑器-拖动节点.mp4 WOW051-行为树编辑器复制粘贴改变.mp4 WOW050-行为树编辑器删除折叠.mp4 WOW049-行为树自动布局.mp4 WOW048-行为树编辑器创建节点.mp4 WOW047-行为树编辑器-右键菜单.mp4 WOW046-行为树编辑器.mp4 WOW045-光环2.mp4 WOW044-光环1.mp4 WOW043-SpellMod2.mp4 WOW042-SpellMod1.mp4 WOW041-捕获宠物3.mp4 WOW040-捕获宠物2.mp4 WOW039-捕获宠物1.mp4 WOW038-仇恨.mp4 WOW037-AI框架02.mp4 WOW036-AI框架01.mp4 WOW035-暴风雪寒冰箭完善02.mp4 WOW034-暴风雪寒冰箭完善01.mp4 WOW033-寒冰箭02.mp4 WOW032-寒冰箭01.mp4 WOW031-暴风雪02.mp4 WOW030-暴风雪01.mp4 WOW029-自动攻击.mp4 WOW028-普通攻击03.mp4 WOW027-普通攻击02.mp4 WOW026-普通攻击01.mp4 WOW025-Buff05.mp4 WOW024-Buff04.mp4 WOW023-Buff03.mp4 WOW022-Buff02.mp4 WOW021-Buff01.mp4 WOW020-完善目标选择.mp4 WOW019-技能指示器.mp4 WOW018-技能施放-目标选择-服务器.mp4 WOW017-技能施放.mp4 WOW016-技能施放技能CD.mp4 WOW015-实现一个行为树3.mp4 WOW014-实现一个行为树2.mp4 WOW013-实现一个行为树.mp4 WOW012-技能跟buff配置支持.mp4 WOW011-技能跟Buff的配置.mp4 WOW010-技能框架设计.mp4 WOW009-魔兽技能框架改进.mp4 WOW008-魔兽技能分析.mp4 WOW007-Animator.mp4 WOW006-怪物创建以及巡逻AI.mp4 WOW005-Numeric改造.mp4 WOW004-Recast移动-修正.mp4 WOW004-Recast移动.mp4 WOW003-InputSystem.mp4 WOW002-摄像机Cinemachine.mp4 WOW001-人物进入场景.mp4 ## [类魔兽世界MMO开发课程介绍视频](https://www.bilibili.com/video/BV1GV6DYBEpo/?vd_source=e55f8234b8f8039504cbf131082c93dd) 后续实现其它技能以及天赋,装备,成就,副本,分线等等 大纲大致如上,感觉大纲还有很多没有列举出来,每节课感觉内容都很多。demo中涉及到大量对ET框架的优化以及修改,就不一一说明了 该课程是持续开发持续录制的,内容并不止上面的内容,后面还会会一直增加新功能,并且配套对应的课程视频,这就相当于新版的ET框架,开源版本不再更新,只持续更新课程版本 每周都有直播,回答学员课程中的疑问 # 需要魔兽世界开发课程跟代码的请加下面QQ群,群里找群主购买 # __讨论QQ群 : 474643097__ (已满) 新手请加新手群:688514974 --- # 三猩公司拖欠我的咨询费用的问题,最近得到了解决。具体原因如下: 1. 三猩公司老板有多家公司,人不在三猩公司,而是在异地,老板托一个陈姓主管全权管理三猩公司。 2. 老板每个月都会转费用给陈姓主管做公司房租,员工工资等等。 3. 找我做咨询也是该陈姓主管,一直是他跟我联系,咨询费用也是陈姓主管转给我的。 4. 后来陈姓主管挪用了三猩公司的正常经营费用(个人猜测可能是网赌),不仅没交三猩公司房租,也没发员工工资,也拖欠了我的咨询费。 5. 最近半年老板发现此事。一直在追责陈姓主管,后面也主动联系到我,转给了我咨询费用。 --- --- # 商业项目(已出现多个一个人开发的MMO上线,足以证明ET的强大): 1. [千古风流](https://www.qiangu.com/) 大型mmo,团队100人2年开发完成 2. [神选誓约](https://www.taptap.cn/app/248095) 3. [危境](https://www.taptap.cn/app/271100) 一个技术一个策划开发的mmo,抖音可以搜索危境看直播 4. 魔力回忆 许木大厨一个人开发的魔力宝贝 5. [新生](https://store.steampowered.com/app/1704940/_/) steam上,一个人开发的科幻mmo 6. [怒斩屠龙之乾坤剑指](http://wan.rxqu.com/mobile/downfile/index/gid/9.html) 传奇项目 7. [魔法点点2](https://www.taptap.com/app/227804) 一个人开发 8. [养不大](https://www.taptap.com/app/71064) 9. 天天躲猫猫2(ios2019春节下载排行19) 10. [牛虎棋牌](https://gitee.com/ECPS_admin/PlanB) 11. [五星麻将](https://github.com/wufanjoin/fivestar) 12. [代号肉鸽:无限](https://www.taptap.cn/app/247225) 一个人开发 13. [卡车人生](https://www.kxdw.com/android/130033.html) 14. [掌门太闲](https://www.taptap.cn/app/361952) 一个人开发 15. [万世镜](https://www.taptap.cn/app/235872) 16. [Culture & Chest](https://play.google.com/store/apps/details?id=com.Inutan.CultureChest) 17. [史莱姆大乱斗](https://www.taptap.cn/app/228923) 18. [魔灵幻想](https://www.taptap.cn/app/337878) 一个技术 19. [Tank Clash: Blitz!](https://apps.apple.com/us/app/tank-clash-blitz/id6477913248) 20. [黑山之巅](https://store.steampowered.com/app/2015760/_/?l=schinese) 21. [赴江湖](https://www.taptap.cn/app/712191) 一款武侠类MMO 21. [欢迎来到魔王城](https://store.steampowered.com/app/3357950) 结合塔防建造养成的二次元纸片老婆游戏 --- # ET9最新Package,在这里搜索插件[ET Packages](https://github.com/orgs/ET-Packages/packages),安装请看运行指南 1. cn.etetet.mongodb 数据库补丁工具增强db包,用于团队或者线上对数据库数据打补丁,售价100元 2. [cn.etetet.yiuinumeric](https://lib9kmxvq7k.feishu.cn/wiki/GHDOwsmy0iQQMok3gU7cgxbpn7x) 增强版数值系统,需要搭配下面的luban插件使用,售价100元 3. [cn.etetet.yiuiluban](https://lib9kmxvq7k.feishu.cn/wiki/W1ylwC9xDip1YQk4eijcxgO9nh0) 无缝替换cn.etetet.excel成luban配置,售价100元0. 限免 购买luban包即赠送以下所有 1. [cn.etetet.yiuicondition](https://lib9kmxvq7k.feishu.cn/wiki/Kc0awA9EXiRJylkreL9cfLJFnsg) 条件系统 2. [cn.etetet.yiuigameobjectpool](https://lib9kmxvq7k.feishu.cn/wiki/UyigweBFXipNJnkCIY6coHzFnSc) 游戏对象缓存池 3. [cn.etetet.yiuiaudio](https://lib9kmxvq7k.feishu.cn/wiki/ECbzwVPmDiQNY9ktJHecCuGsnig) 音乐音效 4. [cn.etetet.yiuivideo](https://lib9kmxvq7k.feishu.cn/wiki/Wt7twM5nciZl5rkKf7ScpGGAn4c) 视频播放 5. [cn.etetet.yiuidamagetips](https://lib9kmxvq7k.feishu.cn/wiki/Yt8PwrobfiYijDkuZTWcsGyJnFh) 伤害提示 4. cn.etetet.webgl 客户端支持打包webgl,前后端websocket连接,注意微信小游戏需要自己接入,由于已经支持了webgl,小游戏接入并不复杂,不接入小游戏主要是因为有人会使用团结有人用unity,没法统一版本,售价999元(购买过et8 webgl的可以免费升级) 5. FGUI系列插件,icepower开发,免费 6. [YIUI系列插件](https://lib9kmxvq7k.feishu.cn/wiki/XJxLwzTlViqD5TkSAw1c32Rqnd1) ET-UGUI框架 (yiyi开发,免费) # ET9.0 发布!(西施) 1. ET9改成了Package模式,任何功能可以制作成ET Package放到ET Package中心,用户能极其简单的集成到自己项目中。制作package请看上面的制作指南 2. ETTask实现了传递上下文功能,可以去掉烦人的CancellationToken传递 感谢 花语梦蝶 提出的的新思路 3. Entity简化,去掉了ChildrenDB跟ComponentDB,自定义序列化SortedDictionary,可以指定某个Child或者Componet跟不跟随Parent序列化 4. Kcp改成非托管内存分配,GC更少,感谢 Molth 提交的代码 5. LICENSE更改了,为了保证让的大家修改的代码所有人都能方便的使用,ET9使用了新的LICENSE,具体请看LICENSE文件 6. YIUI Package 感谢亦亦开发 7. FGUI Package 感谢IcePower开发 # [ET Packages](https://github.com/orgs/ET-Packages/packages) # [ET Packages制作指南](./Book/8.1ET%20Package制作指南.md) # [ET论坛](https://et-framework.cn) # [ET视频教程](https://community.uwa4d.com/search?keyword=ET&scope=1) # [运行指南](./Book/1.1运行指南.md) # [分析器说明](https://www.yuque.com/u28961999/yms0nt/) # 熊猫的三门课程,需要请加QQ 80081771: 1. [网络游戏架构设计](https://www.bilibili.com/video/BV1h84y1G7aH/?spm_id_from=333.999.0.0&vd_source=e55f8234b8f8039504cbf131082c93dd) 基于ET7.2,共27集,讲解ET7.2的框架设计细节 2. [帧同步设计](https://www.bilibili.com/video/BV1tX4y1C7pM/?share_source=copy_web&vd_source=001b901865c99550d1b2a8cd663695d4) 基于ET8,共12集,讲解预测回滚帧同步设计 3. [多线程架构设计](https://www.bilibili.com/video/BV1Ah4y1f7QT/?spm_id_from=333.999.0.0&vd_source=e55f8234b8f8039504cbf131082c93dd) 基于ET8,共11集,讲解ET8的多线程设计 4. WebGL小游戏框架,基于ET8,完善的网络,配置,热更等等,跟ET8使用一样 # ET8.1 发布! 8.1跟8区别不大,代码结构完全没有变化,主要是编译方式的修改,可以在Unity中按F6进行编译,也可以在IDE中进行编译,运行中reload可以先按F6编译,再按F7进行热重载 # ET8 发布! 貂蝉 1. 多线程多进程架构,架构更加灵活强大,多线程设计详细内容请看多线程设计课程 2. 抽象出纤程(Fiber)的概念,类似erlang的进程,非常轻松的创建多个纤程,利用多核,仍然是单线程开发的体验 3. 纤程调度: 主线程,线程池,每个纤程一个线程,3种调度方式 4. Fiber间通信的Actor消息机制 5. Entity方面,domain改成IScene,只要实现IScene接口,Entity就是domain,这样定义domain更加自由 6. 预测回滚的帧同步实现 想详细了解可以看帧同步课程 7. protobuf换成了memorypack,实现无gc的网络 8. 纯C#版的kcp库,性能非常强,由sj提交 9. 热更dll改成用ide编译,更加方便 10. sj利用source generater实现了代码自动模板功能,目前可以自动生成System类,开发者只需要定义Awake Update静态方法即可,特别方便 11. sj开发了分析器,实现了EntitySystemOf,根据entity接口一键生成对应的system方法 12. 客户端利用fiber实现网络独立线程(demo已实现),甚至可以把逻辑跟表现使用独立的纤程,更好的利用多核 13. 帧同步demo直接利用纤程创建房间,更加方便 14. 纯c#版寻路dotrecast,至此ET已经完全C#化,没有任何cpp代码了 15. kcp跟软路由底层同时支持tcp跟websocket,当udp联不通的情况下,可以切换成tcp Websocket,并且支持运行时动态切换,玩家不掉线! 16. 集成了sj的非托管容器库,性能爆炸 # 用ET的18个理由 1. 多进程多线程Actor架构,客户端跟服务端都可以轻松创建纤程(fiber)利用多核,比如客户端网络一个纤程,寻路一个纤程,帧同步逻辑层一个纤程,表现层一个纤程 2. async await协程同步代码编写,避免回调地狱 3. 0GC消耗,超强的MemoryPack序列化, 超强的网络层性能 4. kcp支持,网络响应非常迅速,并且闪断wifi 4g都不会导致掉线,做竞技游戏必备 5. kcp底层可以使用tcp udp Websocket协议,当udp联不通的情况下,可以切换成tcp Websocket,并且支持运行时动态切换,玩家不掉线! 6. 软路由防攻击设计,买些垃圾主机就可以防住黑客攻击,比买高防省钱多了,并且用户不会掉线 7. 双端C#开发,前后端共享代码,C#本身性能极强,仅次于CPP,不需要学一些乱起八糟的语言,很多独立游戏开发者,一个人就能用ET开发mmorpg游戏 8. 强大的编译分析器,编译器就能帮助大家写出正确的ET风格的代码 9. 客户端hybridclr热更新支持 10. 客户端服务端均支持运行时热重载,客户端服务端不需要关闭进程就能修改代码,大大提升了开发效率以及运营效率 11. 完善的demo,源码带有状态同步跟预测回滚的帧同步demo 12. 完善的机器人开发机制,机器人直接共享客户端逻辑代码,减少95%机器人开发工作量,接入ai机器人非常轻松。大规模机器人压测,轻而易举 13. 强大的ai开发机制,比行为树更加容易 14. 强大的单元测试开发机制,每个单元测试都是整个游戏环境,不用搞mock隔离,开发起来非常轻松 15. 优美的程序结构,数据跟方法完全分离 16. all in one的开发体验,开发时只需要启动unity,发布的时候又可以单独发布服务端,并且可以跨windows跟linux平台 17. 客户端服务端数据开发期完全可视化,开启ENABLE_VIEW宏即可在Unity Hierarchy面板中看到客户端跟服务端的所有的Entity对象以及字段的内容 18. WebGL以及微信小游戏支持,有ET8的webgl版本,开发体验跟ET8完全一致,无缝对接ET8的服务器 # Benchmark 100W Ping Pong 平均耗时4秒左右,平均每秒收发20W的消息。这个网络性能远远超过主线程的需求,大家可以自己测试一下,测试方法: Unity Menu->ServerTools select Benchmark, Start Watcher。然后在Logs目录,打开Debug日志等一会所有连接完成就能看到下面的日志了。 2022-12-02 22:19:48.9837 (C2G_BenchmarkHandler.cs:13) benchmark count: 1000001 2022-12-02 22:19:53.4621 (C2G_BenchmarkHandler.cs:13) benchmark count: 2000001 2022-12-02 22:19:57.0416 (C2G_BenchmarkHandler.cs:13) benchmark count: 3000001 2022-12-02 22:20:00.6186 (C2G_BenchmarkHandler.cs:13) benchmark count: 4000001 2022-12-02 22:20:04.1384 (C2G_BenchmarkHandler.cs:13) benchmark count: 5000001 2022-12-02 22:20:08.2236 (C2G_BenchmarkHandler.cs:13) benchmark count: 6000001 2022-12-02 22:20:12.2842 (C2G_BenchmarkHandler.cs:13) benchmark count: 7000001 2022-12-02 22:20:15.8544 (C2G_BenchmarkHandler.cs:13) benchmark count: 8000001 2022-12-02 22:20:19.4085 (C2G_BenchmarkHandler.cs:13) benchmark count: 9000001 2022-12-02 22:20:24.2969 (C2G_BenchmarkHandler.cs:13) benchmark count: 10000001 2022-12-02 22:20:41.1448 (C2G_BenchmarkHandler.cs:13) benchmark count: 11000001 2022-12-02 22:20:44.7174 (C2G_BenchmarkHandler.cs:13) benchmark count: 12000001 2022-12-02 22:20:48.3188 (C2G_BenchmarkHandler.cs:13) benchmark count: 13000001 2022-12-02 22:20:51.7793 (C2G_BenchmarkHandler.cs:13) benchmark count: 14000001 2022-12-02 22:20:55.3379 (C2G_BenchmarkHandler.cs:13) benchmark count: 15000001 2022-12-02 22:20:58.8810 (C2G_BenchmarkHandler.cs:13) benchmark count: 16000001 2022-12-02 22:21:02.5156 (C2G_BenchmarkHandler.cs:13) benchmark count: 17000001 2022-12-02 22:21:06.0132 (C2G_BenchmarkHandler.cs:13) benchmark count: 18000001 2022-12-02 22:21:09.5320 (C2G_BenchmarkHandler.cs:13) benchmark count: 19000001 # ET7 发布! 18岁亦菲 1. 调整结构,机器人工程与服务器合并,更易使用,一个进程同时可以做server,也能创建机器人,真正的ALL IN ONE! -- 已实现 2. 客户端跟服务端合并,服务端代码全部放在了客户端,客户端中可以带一个服务端,开发超级方便,服务端发布的时候可以选择发布成Dotnet也可以发布成UnityServer,终极All IN ONE -- 已实现 3. Entity可视化,客户端跟服务端所有的Entity都实现了可视化,开启ENABLE_CODES宏,运行游戏,查看Hierarchy面板,展开Init/Global/Scene(Process)即可看到 -- 已实现 4. 因为所有代码都在Unity中,所以开发ET插件变得非常容易,直接使用Unity导入导出即可 -- 已实现 5. 增加软路由,可以防各种网络攻击而不影响正常玩家,网游必备!-- 已实现 6. 各种事件跟网络消息订阅带上DomainSceneType,更精确,更不容易出错 -- 已实现 7. sj兄弟添加了各种分析器,分析器保证了写出的代码必须符合ET规范,否则编译不通过!(这点ET6也增加上了) -- 已实现 8. ET7集成了huatuo热更新库。 注意!(不要混淆客户端热更新跟服务端热更新,服务端热更新,ET一直都有) 9. 网络改成独立线程,序列化反序列化都在网络线程处理,主线程压力大大减轻。并且重新整理了网络层代码,更优美了 10. 集成Unity.Mathematic数学库,逻辑层客户端跟服务端都使用这一套数学库,这样服务端跟客户端完全统一了 11. ENABLE_CODES模式下拆分成4个程序集,解决分析器失效的问题 12. Game管理的Singleton增加ISingletonUpdate跟ISingletonLateUpdate接口,实现相应的接口即可执行对应的Update跟LateUpdate方法,Game类解除了跟EventSystem等单间类的耦合关系 13. Actor消息判断如果是发向自己的进程则不用通过网络,直接处理即可,大大提升性能 # ET6 发布!ET6相比ET5有巨大变化,可以说是凤姐变亦菲,6.0拥有如下惊人的特点 1. 客户端逻辑全热更新(基于ILRuntime),没有不能更的部分 2. 客户端服务端均可热重载,开发不用重启客户端服务端即可修改逻辑代码,开发极其方便 3. 机器人框架,ET6的客户端的逻辑跟表现分离,机器人程序直接共享利用客户端的逻辑层代码做压测,只需要极少代码即可做出机器人,方便压测服务端 4. 测试用例框架,利用客户端的逻辑层代码写单元测试,每个单元测试都是完整的游戏环境,无需各种恶心的mock 5. AI框架,比行为树更加方便,写AI比写UI还简单 6. 新的服务端架构,极其优美 7. 内外网kcp网络,性能强劲,搭配软路由模块,可以防各种网络攻击 # ET开发的商业mmo项目千古风流成功上线,64核128G内存的单服单物理机1.5W在线(实际线上策划为了生态限制为单服6000人同时在线,6000人的话cpu消耗约为30%)。为了堆栈行号正常,线上跑得是Debug版,如果使用Release版开启优化,性能还能翻一倍,达到单物理机3W在线!上线5个月来十分稳定。千古风流使用了ET框架从零开发,用时两年,这个开发速度可以说无人出其右。千古风流的成功上线证明了ET具备开发任何大型游戏的能力,开发速度,开发效率都令人叹为观止!千古风流使用到的客户端服务器技术: 1. 动态副本跟分线,按需分配,用完回收 2. 分线合线,分线人数较少会把多条线合并。合线功能基本上其它mmo游戏很少见到 3. 客户端服务端场景无缝切换,也就是无缝大世界技术 4. 跨服副本,跨服战场 5. 前后端一体化,利用客户端代码开发服务器压测机器人,4台24核机器轻松模拟1W人做任务 6. 千古风流各种ai设计,使用ET的全新开发的ai框架,使ai开发简单到跟写ui一样简单 7. 测试用例框架,大部分重要系统,千古风流都写了测试用例,跟市面上的测试用例不同,每个千古风流的测试用例都是一个完整的游戏环境,针对协议级别,不需要搞各种接口去mock。写起来非常快速。 8. 九宫格的aoi实现,动态调整看见的玩家,降低服务器负载 9. 防攻击,千古风流开发了软路由功能,即使攻击也只能攻击到软路由,一旦被攻击,玩家客户端发现几秒钟无响应,即可动态切换到其它软路由,用户几乎无感知。整个过程客户端网络连接不断开,数据不丢失。 10. 还有很多很多,这里就不啰嗦了 # ET的介绍: ET是一个开源的游戏客户端(基于unity3d)服务端双端框架,服务端是使用C# .net core开发的分布式游戏服务端,其特点是开发效率高,性能强,双端共享逻辑代码,客户端服务端热更机制完善,同时支持可靠udp tcp websocket协议,支持服务端3D recast寻路等等 # ET的功能: ### 1.可用VS单步调试的分布式服务端,N变1 一般来说,分布式服务端要启动很多进程,一旦进程多了,单步调试就变得非常困难,导致服务端开发基本上靠打log来查找问题。平常开发游戏逻辑也得开启一大堆进程,不仅启动慢,而且查找问题及其不方便,要在一堆堆日志里面查问题,这感觉非常糟糕,这么多年也没人解决这个问题。ET框架使用了类似守望先锋的组件设计,所有服务端内容都拆成了一个个组件,启动时根据服务器类型挂载自己所需要的组件。这有点类似电脑,电脑都模块化的拆成了内存,CPU,主板等等零件,搭配不同的零件就能组装成一台不同的电脑,例如家用台式机需要内存,CPU,主板,显卡,显示器,硬盘。而公司用的服务器却不需要显示器和显卡,网吧的电脑可能不需要硬盘等。正因为这样的设计,ET框架可以将所有的服务器组件都挂在一个服务器进程上,那么这个服务器进程就有了所有服务器的功能,一个进程就可以作为整组分布式服务器使用。这也类似电脑,台式机有所有的电脑组件,那它也完全可以当作公司服务器使用,也可以当作网吧电脑。 ### 2.随意可拆分功能的分布式服务端,1变N 分布式服务端要开发多种类型的服务器进程,比如Login server,gate server,battle server,chat server friend server等等一大堆各种server,传统开发方式需要预先知道当前的功能要放在哪个服务器上,当功能越来越多的时候,比如聊天功能之前在一个中心服务器上,之后需要拆出来单独做成一个服务器,这时会牵扯到大量迁移代码的工作,烦不胜烦。ET框架在平常开发的时候根本不太需要关心当前开发的这个功能会放在什么server上,只用一个进程进行开发,功能开发成组件的形式。发布的时候使用一份多进程的配置即可发布成多进程的形式,是不是很方便呢?随便你怎么拆分服务器。只需要修改极少的代码就可以进行拆分。不同的server挂上不同的组件就行了嘛! ### 3.跨平台的分布式服务端 ET框架使用C#做服务端,现在C#是完全可以跨平台的,在linux上安装.netcore,即可,不需要修改任何代码,就能跑起来。性能方面,现在.netcore的性能非常强,比lua,python,js什么快的多了。做游戏服务端完全不在话下。平常我们开发的时候用VS在windows上开发调试,发布的时候发布到linux上即可。ET框架还提供了一键同步工具,打开unity->tools->rsync同步,即可同步代码到linux上 ```bash ./Run.sh Config/StartConfig/192.168.12.188.txt ``` 即可编译启动服务器。 ### 4.提供协程支持 C#天生支持异步变同步语法 async和await,比lua,python的协程强大的多,新版python以及javascript语言甚至照搬了C#的协程语法。分布式服务端大量服务器之间的远程调用,没有异步语法的支持,开发将非常麻烦。所以java没有异步语法,做单服还行,不适合做大型分布式游戏服务端。例如: ```c# // 发送C2R_Ping并且等待响应消息R2C_Ping R2C_Ping pong = await session.Call(new C2R_Ping()) as R2C_Ping; Log.Debug("收到R2C_Ping"); // 向mongodb查询一个id为1的Player,并且等待返回 Player player = await Game.Scene.GetComponent().Query(1); Log.Debug($"打印player name: {player.Name}") ``` 可以看出,有了async await,所有的服务器间的异步操作将变得非常连贯,不用再拆成多段逻辑。大大简化了分布式服务器开发 ### 5.提供类似erlang的actor消息机制 erlang语言一大优势就是位置透明的消息机制,用户完全不用关心对象在哪个进程,拿到id就可以对对象发送消息。ET框架也提供了actor消息机制,实体对象只需要挂上MailBoxComponent组件,这个实体对象就成了一个Actor,任何服务器只需要知道这个实体对象的id就可以向其发送消息,完全不用关心这个实体对象在哪个server,在哪台物理机器上。其实现原理也很简单,ET框架提供了一个位置服务器,所有挂载MailBoxComponent的实体对象都会将自己的id跟位置注册到这个位置服务器,其它服务器向这个实体对象发送消息的时候如果不知道这个实体对象的位置,会先去位置服务器查询,查询到位置再进行发送。 ### 6.提供服务器不停服动态更新逻辑功能 热更是游戏服务器不可缺少的功能,ET框架使用的组件设计,可以做成守望先锋的设计,组件只有成员,无方法,将所有方法做成扩展方法放到热更dll中,运行时重新加载dll即可热更所有逻辑。 ### 7.客户端使用C#热更新,热更新一键切换 可以使用csharp.lua或者ILRuntime稍加改造即可做客户端热更。再也不用使用狗屎lua了,客户端可以实现所有逻辑热更新,包括协议,config,ui等等。 ### 8.客户端热重载 开发不用重启客户端即可修改客户端逻辑代码,开发极其方便 ### 9.客户端服务端用同一种语言,并且共享代码 下载ET框架,打开服务端工程,可以看到服务端引用了客户端很多代码,通过引用客户端代码的方式实现了双端共享代码。例如客户端服务端之间的网络消息两边完全共用一个文件即可,添加一个消息只需要修改一遍。 ### 10.KCP ENET TCP Websocket协议无缝切换 ET框架不但支持TCP,而且支持可靠的UDP协议(ENET跟KCP),ENet是英雄联盟所使用的网络库,其特点是快速,并且网络丢包的情况下性能也非常好,这个我们做过测试TCP在丢包5%的情况下,moba游戏就卡的不行了,但是使用ENet,丢包20%仍然不会感到卡。非常强大。框架还支持使用KCP协议,KCP也是可靠UDP协议,据说比ENET性能更好,使用kcp请注意,需要自己加心跳机制,否则20秒没收到包,服务端将断开连接。协议可以无缝切换。 ### 11. 3D Recast寻路功能 可以Unity导出场景数据,给服务端做recast寻路。做MMO非常方便,demo演示了服务端3d寻路功能 ### 12. 服务端支持repl,也可以动态执行一段新代码 这样就可以打印出进程中任何数据,大大简化了服务端查找问题的难度,开启repl方法,直接在console中输入repl回车即可进入repl模式 ### 13.提供客户端机器人框架支持 几行代码即可创建机器人登录游戏。机器人压测轻而易举,机器人跟正常的玩家完全一样,上线前用机器人做好压测,大大降低上线崩溃几率 ### 14.AI框架 ET的AI框架让AI编写比UI还简单 ### 15.测试用例框架 跟市面上的测试用例不同,ET的测试用例都是一个完整的游戏环境,针对协议级别,不需要搞各种接口去mock。写起来非常快速 ### 16.还有很多很多功能,我就不详细介绍了 a.及其方便检查CPU占用和内存泄漏检查,vs自带分析工具,不用再为性能和内存泄漏检查而烦恼 b.使用NLog库,打log及其方便,平常开发时,可以将所有服务器log打到一个文件中,再也不用一个个文件搜索log了 c.统一使用Mongodb的bson做序列化,消息和配置文件全部都是bson或者json,并且以后使用mongodb做数据库,再也不用做格式转换了。 d.提供一个同步工具 ET框架是一个强大灵活的分布式服务端架构,完全可以满足绝大部分大型游戏需求。使用这套框架,客户端开发者就可以自己完成双端开发,节省大量人力物力,节省大量沟通时间。 相关网站: [ET论坛](https://et-framework.cn) 群友分享: [行为树与fgui分支(Duke Chiang开发维护)](https://github.com/DukeChiang/ET.git) [ET学习笔记系列(烟雨迷离半世殇写)](https://www.lfzxb.top/) [图形渲染与ET学习笔记(咲夜詩写)](https://acgmart.com/) [框架服务端运行流程](http://www.cnblogs.com/fancybit/p/et1.html) [ET启动配置](http://www.cnblogs.com/fancybit/p/et2.html) [框架demo介绍](http://www.jianshu.com/p/f2ea0d26c7c1) [linux部署](http://gad.qq.com/article/detail/35973) 群友demo: 1. [斗地主(客户端服务端)](https://github.com/Viagi/LandlordsCore) 2. [背包系统](https://gitee.com/ECPS_admin/planc) 3. [移动端渲染技术demo](https://github.com/Acgmart/Sekia_TechDemo) 4. [球球大作战(ET7.2)](https://github.com/FlameskyDexive/Legends-Of-Heroes) 视频教程: [字母哥ET6.0教程](https://edu.uwa4d.com/course-intro/1/375) [肉饼老师主讲](http://www.taikr.com/my/course/972) [官剑铭主讲](https://edu.manew.com/course/796) [ET新手教程-初见主讲](https://pan.baidu.com/s/1a5-j2R5QctZpC9n3sMC9QQ) 密码: ru1j [ET新手教程新版-初见主讲](https://www.bilibili.com/video/av33280463/?redirectFrom=h5) [ET在Mac上运行指南-L主讲](https://pan.baidu.com/s/1VUQbdd1Yio7ULFXwAv7X7A) 密码: l3e3 [ET框架系列教程-烟雨主讲-6.0版本](https://space.bilibili.com/33595745/favlist?fid=759596845&ftype=create) .net core 游戏资源分享 [各种dotnet core项目收集](https://github.com/thangchung/awesome-dotnet-core) __讨论QQ群 : 474643097__ # 支付宝捐赠 ![使用支付宝对该项目进行捐赠](https://github.com/egametang/ET/blob/master/Book/donate.png) # 友情链接 [Box2DSharp](https://github.com/Zonciu/Box2DSharp) box2d的C#移植版,性能很强 [xasset](https://github.com/xasset/xasset) 致力于为 Unity 项目提供了一套 精简稳健 的资源管理环境 [QFramework](https://github.com/liangxiegame/QFramework) Your first K.I.S.S Unity3d Framework [ET UI框架](https://github.com/zzjfengqing/ET-EUI) 字母哥实现的UI框架,ET风格,各种事件分发 [Luban](https://github.com/focus-creative-games/luban) 适用于大中型项目的游戏配置解决方案 [ET-YIUI](https://github.com/LiShengYang-yiyi/YIUI/tree/YIUI-ET7.2) ETUI框架 ================================================ FILE: Scripts/Publish-linux-x64.ps1 ================================================ function PublishLinux { dotnet publish ET.sln -r linux-x64 --no-self-contained --no-dependencies -c Release $path = "Publish\linux-x64" Remove-Item $path\Bin\ -Recurse -ErrorAction Ignore Copy-Item .\Bin\linux-x64\publish -Destination $path\Bin -Recurse -Force Remove-Item $path\Packages -Recurse -ErrorAction Ignore $matchingPaths = Get-ChildItem -Path "Packages" -Directory $matchingPaths | ForEach-Object { $relativePath = Join-Path $_ "Config" $fullConfigPath = Join-Path "Packages" $relativePath if (Test-Path $fullConfigPath -PathType Container) { Write-Host "Find Config :"$fullConfigPath $targetPath = Join-Path $path $fullConfigPath Write-Host "CopyTo :"$targetPath Copy-Item $fullConfigPath -Destination $targetPath -Recurse -Force } } pause } cd ../ PublishLinux ================================================ FILE: Unity.sln.DotSettings ================================================  <root><filter><namespace_mask>Boo</namespace_mask><has_type_parameters>Any</has_type_parameters><element_kind>Any</element_kind></filter></root> True True True <> True False False True DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW SUGGESTION SUGGESTION SUGGESTION SUGGESTION DO_NOT_SHOW SUGGESTION SUGGESTION SUGGESTION DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW <?xml version="1.0" encoding="utf-16"?><Profile name="Unity"><CSArrangeThisQualifier>True</CSArrangeThisQualifier><CSRemoveCodeRedundancies>True</CSRemoveCodeRedundancies><CSUseVar><BehavourStyle>DISABLED</BehavourStyle><LocalVariableStyle>IMPLICIT_WHEN_INITIALIZER_HAS_TYPE</LocalVariableStyle><ForeachVariableStyle>IMPLICIT_EXCEPT_SIMPLE_TYPES</ForeachVariableStyle></CSUseVar><CSOptimizeUsings><OptimizeUsings>True</OptimizeUsings><EmbraceInRegion>False</EmbraceInRegion><RegionName></RegionName></CSOptimizeUsings><CSShortenReferences>True</CSShortenReferences><CSReformatCode>True</CSReformatCode><RemoveCodeRedundancies>True</RemoveCodeRedundancies><CSCodeStyleAttributes ArrangeTypeAccessModifier="True" ArrangeTypeMemberAccessModifier="True" SortModifiers="True" RemoveRedundantParentheses="True" AddMissingParentheses="True" ArrangeBraces="True" ArrangeAttributes="True" ArrangeArgumentsStyle="True" ArrangeCodeBodyStyle="True" ArrangeVarStyle="True" /></Profile> Unity True Required Required Required Required Field, Property, Event, Method False False 2 JOIN ALWAYS_ADD ALWAYS_ADD ALWAYS_ADD ALWAYS_ADD ALWAYS_ADD ALWAYS_ADD NONE 1 1 True False True False True 1 private public protected internal new abstract virtual override sealed static readonly extern unsafe volatile async NEVER ALWAYS ALWAYS ALWAYS False NEVER False NEVER True ALWAYS_USE LINE_BREAK LINE_BREAK True True False False True False False True False True False False False False False WRAP_IF_LONG 150 WRAP_IF_LONG WRAP_IF_LONG WRAP_IF_LONG True True False TwoSteps TwoSteps False False <?xml version="1.0" encoding="utf-8" ?> <!-- I. Overall I.1 Each pattern can have <Match>....</Match> element. For the given type declaration, the pattern with the match, evaluated to 'true' with the largest weight, will be used I.2 Each pattern consists of the sequence of <Entry>...</Entry> elements. Type member declarations are distributed between entries I.3 If pattern has RemoveAllRegions="true" attribute, then all regions will be cleared prior to reordering. Otherwise, only auto-generated regions will be cleared I.4 The contents of each entry is sorted by given keys (First key is primary, next key is secondary, etc). Then the declarations are grouped and en-regioned by given property II. Available match operands Each operand may have Weight="..." attribute. This weight will be added to the match weight if the operand is evaluated to 'true'. The default weight is 1 II.1 Boolean functions: II.1.1 <And>....</And> II.1.2 <Or>....</Or> II.1.3 <Not>....</Not> II.2 Operands II.2.1 <Kind Is="..."/>. Kinds are: class, struct, interface, enum, delegate, type, constructor, destructor, property, indexer, method, operator, field, constant, event, member II.2.2 <Name Is="..." [IgnoreCase="true/false"] />. The 'Is' attribute contains regular expression II.2.3 <HasAttribute CLRName="..." [Inherit="true/false"] />. The 'CLRName' attribute contains regular expression II.2.4 <Access Is="..."/>. The 'Is' values are: public, protected, internal, protected internal, private II.2.5 <Static/> II.2.6 <Abstract/> II.2.7 <Virtual/> II.2.8 <Override/> II.2.9 <Sealed/> II.2.10 <Readonly/> II.2.11 <ImplementsInterface CLRName="..."/>. The 'CLRName' attribute contains regular expression II.2.12 <HandlesEvent /> --> <Patterns xmlns="urn:shemas-jetbrains-com:member-reordering-patterns"> <!--Do not reorder COM interfaces and structs marked by StructLayout attribute--> <Pattern> <Match> <Or Weight="100"> <And> <Kind Is="interface"/> <Or> <HasAttribute CLRName="System.Runtime.InteropServices.InterfaceTypeAttribute"/> <HasAttribute CLRName="System.Runtime.InteropServices.ComImport"/> </Or> </And> <HasAttribute CLRName="System.Runtime.InteropServices.StructLayoutAttribute"/> </Or> </Match> </Pattern> <!--Special formatting of NUnit test fixture--> <Pattern RemoveAllRegions="true"> <Match> <And Weight="100"> <Kind Is="class"/> <HasAttribute CLRName="NUnit.Framework.TestFixtureAttribute" Inherit="true"/> </And> </Match> <!--Setup/Teardow--> <Entry> <Match> <And> <Kind Is="method"/> <Or> <HasAttribute CLRName="NUnit.Framework.SetUpAttribute" Inherit="true"/> <HasAttribute CLRName="NUnit.Framework.TearDownAttribute" Inherit="true"/> <HasAttribute CLRName="NUnit.Framework.FixtureSetUpAttribute" Inherit="true"/> <HasAttribute CLRName="NUnit.Framework.FixtureTearDownAttribute" Inherit="true"/> </Or> </And> </Match> <Group Region="Setup/Teardown"/> </Entry> <!--All other members--> <Entry/> <!--Test methods--> <Entry> <Match> <And Weight="100"> <Kind Is="method"/> <HasAttribute CLRName="NUnit.Framework.TestAttribute" Inherit="false"/> </And> </Match> <Sort> <Name/> </Sort> </Entry> </Pattern> <!--Default pattern--> <Pattern> <!--public delegate--> <Entry> <Match> <And Weight="100"> <Access Is="public"/> <Kind Is="delegate"/> </And> </Match> <Sort> <Name/> </Sort> <Group Region="Delegates"/> </Entry> <!--public enum--> <Entry> <Match> <And Weight="100"> <Access Is="public"/> <Kind Is="enum"/> </And> </Match> <Sort> <Name/> </Sort> <Group> <Name Region="${Name} enum"/> </Group> </Entry> <!--static fields and constants--> <Entry> <Match> <Or> <Kind Is="constant"/> <And> <Kind Is="field"/> <Static/> </And> </Or> </Match> <Sort> <Kind Order="constant field"/> </Sort> </Entry> <!--instance fields--> <Entry> <Match> <And> <Kind Is="field"/> <Not> <Static/> </Not> </And> </Match> <Sort> <Readonly/> <Name/> </Sort> </Entry> <!--Constructors. Place static one first--> <Entry> <Match> <Kind Is="constructor"/> </Match> <Sort> <Static/> </Sort> </Entry> <!--properties, indexers--> <Entry> <Match> <Or> <Kind Is="property"/> <Kind Is="indexer"/> </Or> </Match> </Entry> <!--interface implementations--> <Entry> <Match> <And Weight="100"> <Kind Is="member"/> <ImplementsInterface/> </And> </Match> <Sort> <ImplementsInterface Immediate="true"/> </Sort> <Group> <ImplementsInterface Immediate="true" Region="${ImplementsInterface} Members"/> </Group> </Entry> <!--all other members--> <Entry/> <!--nested types--> <Entry> <Match> <Kind Is="type"/> </Match> <Sort> <Name/> </Sort> <Group> <Name Region="Nested type: ${Name}"/> </Group> </Entry> </Pattern> </Patterns> UseExplicitType UseExplicitType True False False True False True False False True True <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="I" Suffix="" Style="AaBb" /> <Policy Inspect="False" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="False" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="False" Prefix="__" Suffix="" Style="aaBb" /> <Policy Inspect="False" Prefix="__" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="False" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="False" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="T" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> $object$_On$event$ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy><Descriptor Staticness="Instance" AccessRightKinds="Private" Description="Instance fields (private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static fields (private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /></Policy> $object$_On$event$ <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> BOTH_SIDES True True ObjectBrowser False C:\Apps\Java\jdk1.8.0_131 C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\Roslyn\csi.exe 79 True False Never Never False Never False True False Dark True True True True True True True True True True True True True True True True True True True True True True True True True True False False VS System.CodeDom.Compiler.GeneratedCodeAttribute <data><AttributeFilter ClassMask="System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverageAttribute" IsEnabled="True" /><AttributeFilter ClassMask="System.CodeDom.Compiler.GeneratedCodeAttribute" IsEnabled="True" /></data> True True True True CSharpBracesLayoutPage 247,0 True CSharpOtherPage False False False True True True True True False False True False True 269 True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True True False C:\Users\USER-PC\AppData\Local\JetBrains\Shared\vAny\Sessions False 366 ================================================ FILE: Unity.userprefs ================================================