diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..ddb6ff8 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "visualstudiotoolsforunity.vstuc" + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..da60e25 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,10 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Attach to Unity", + "type": "vstuc", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c176102 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,55 @@ +{ + "files.exclude": { + "**/.DS_Store": true, + "**/.git": true, + "**/.gitmodules": true, + "**/*.booproj": true, + "**/*.pidb": true, + "**/*.suo": true, + "**/*.user": true, + "**/*.userprefs": true, + "**/*.unityproj": true, + "**/*.dll": true, + "**/*.exe": true, + "**/*.pdf": true, + "**/*.mid": true, + "**/*.midi": true, + "**/*.wav": true, + "**/*.gif": true, + "**/*.ico": true, + "**/*.jpg": true, + "**/*.jpeg": true, + "**/*.png": true, + "**/*.psd": true, + "**/*.tga": true, + "**/*.tif": true, + "**/*.tiff": true, + "**/*.3ds": true, + "**/*.3DS": true, + "**/*.fbx": true, + "**/*.FBX": true, + "**/*.lxo": true, + "**/*.LXO": true, + "**/*.ma": true, + "**/*.MA": true, + "**/*.obj": true, + "**/*.OBJ": true, + "**/*.asset": true, + "**/*.cubemap": true, + "**/*.flare": true, + "**/*.mat": true, + "**/*.meta": true, + "**/*.prefab": true, + "**/*.unity": true, + "build/": true, + "Build/": true, + "Library/": true, + "library/": true, + "obj/": true, + "Obj/": true, + "ProjectSettings/": true, + "temp/": true, + "Temp/": true + }, + "dotnet.defaultSolution": "UnityNetcodeWithEntitas.sln" +} \ No newline at end of file diff --git a/Assets/ParrelSync.meta b/Assets/ParrelSync.meta new file mode 100644 index 0000000..82fb126 --- /dev/null +++ b/Assets/ParrelSync.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 711bbd1e36ca42a4bad871eb6a3de1bc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/Editor.meta b/Assets/ParrelSync/Editor.meta new file mode 100644 index 0000000..56fd131 --- /dev/null +++ b/Assets/ParrelSync/Editor.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a31ea7d0315594440839cdb0db6bc411 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/Editor/AssetModBlock.meta b/Assets/ParrelSync/Editor/AssetModBlock.meta new file mode 100644 index 0000000..3bb4f70 --- /dev/null +++ b/Assets/ParrelSync/Editor/AssetModBlock.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8b14e706b1e7cb044b23837e8a70cad9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/Editor/AssetModBlock/EditorQuit.cs b/Assets/ParrelSync/Editor/AssetModBlock/EditorQuit.cs new file mode 100644 index 0000000..dc181d1 --- /dev/null +++ b/Assets/ParrelSync/Editor/AssetModBlock/EditorQuit.cs @@ -0,0 +1,22 @@ +using UnityEditor; +namespace ParrelSync +{ + [InitializeOnLoad] + public class EditorQuit + { + /// + /// Is editor being closed + /// + static public bool IsQuiting { get; private set; } + static void Quit() + { + IsQuiting = true; + } + + static EditorQuit() + { + IsQuiting = false; + EditorApplication.quitting += Quit; + } + } +} \ No newline at end of file diff --git a/Assets/ParrelSync/Editor/AssetModBlock/EditorQuit.cs.meta b/Assets/ParrelSync/Editor/AssetModBlock/EditorQuit.cs.meta new file mode 100644 index 0000000..2296dac --- /dev/null +++ b/Assets/ParrelSync/Editor/AssetModBlock/EditorQuit.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bf2888ff90706904abc2d851c3e59e00 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/Editor/AssetModBlock/ParrelSyncAssetModificationProcessor.cs b/Assets/ParrelSync/Editor/AssetModBlock/ParrelSyncAssetModificationProcessor.cs new file mode 100644 index 0000000..6587482 --- /dev/null +++ b/Assets/ParrelSync/Editor/AssetModBlock/ParrelSyncAssetModificationProcessor.cs @@ -0,0 +1,34 @@ +using UnityEditor; +using UnityEngine; +namespace ParrelSync +{ + /// + /// For preventing assets being modified from the clone instance. + /// + public class ParrelSyncAssetModificationProcessor : UnityEditor.AssetModificationProcessor + { + public static string[] OnWillSaveAssets(string[] paths) + { + if (ClonesManager.IsClone() && Preferences.AssetModPref.Value) + { + if (paths != null && paths.Length > 0 && !EditorQuit.IsQuiting) + { + EditorUtility.DisplayDialog( + ClonesManager.ProjectName + ": Asset modifications saving detected and blocked", + "Asset modifications saving are blocked in the clone instance. \n\n" + + "This is a clone of the original project. \n" + + "Making changes to asset files via the clone editor is not recommended. \n" + + "Please use the original editor window if you want to make changes to the project files.", + "ok" + ); + foreach (var path in paths) + { + Debug.Log("Attempting to save " + path + " are blocked."); + } + } + return new string[0] { }; + } + return paths; + } + } +} \ No newline at end of file diff --git a/Assets/ParrelSync/Editor/AssetModBlock/ParrelSyncAssetModificationProcessor.cs.meta b/Assets/ParrelSync/Editor/AssetModBlock/ParrelSyncAssetModificationProcessor.cs.meta new file mode 100644 index 0000000..7158175 --- /dev/null +++ b/Assets/ParrelSync/Editor/AssetModBlock/ParrelSyncAssetModificationProcessor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 755e570bd21b39440a923056e60f1450 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/Editor/ClonesManager.cs b/Assets/ParrelSync/Editor/ClonesManager.cs new file mode 100644 index 0000000..df47ca0 --- /dev/null +++ b/Assets/ParrelSync/Editor/ClonesManager.cs @@ -0,0 +1,692 @@ +using System.Collections.Generic; +using System.Diagnostics; +using UnityEngine; +using UnityEditor; +using System.Linq; +using System.IO; +using Debug = UnityEngine.Debug; + +namespace ParrelSync +{ + /// + /// Contains all required methods for creating a linked clone of the Unity project. + /// + public class ClonesManager + { + /// + /// Name used for an identifying file created in the clone project directory. + /// + /// + /// (!) Do not change this after the clone was created, because then connection will be lost. + /// + public const string CloneFileName = ".clone"; + + /// + /// Suffix added to the end of the project clone name when it is created. + /// + /// + /// (!) Do not change this after the clone was created, because then connection will be lost. + /// + public const string CloneNameSuffix = "_clone"; + + public const string ProjectName = "ParrelSync"; + + /// + /// The maximum number of clones + /// + public const int MaxCloneProjectCount = 10; + + /// + /// Name of the file for storing clone's argument. + /// + public const string ArgumentFileName = ".parrelsyncarg"; + + /// + /// Default argument of the new clone + /// + public const string DefaultArgument = "client"; + + #region Managing clones + + /// + /// Creates clone from the project currently open in Unity Editor. + /// + /// + public static Project CreateCloneFromCurrent() + { + if (IsClone()) + { + Debug.LogError("This project is already a clone. Cannot clone it."); + return null; + } + + string currentProjectPath = ClonesManager.GetCurrentProjectPath(); + return ClonesManager.CreateCloneFromPath(currentProjectPath); + } + + /// + /// Creates clone of the project located at the given path. + /// + /// + /// + public static Project CreateCloneFromPath(string sourceProjectPath) + { + Project sourceProject = new Project(sourceProjectPath); + + string cloneProjectPath = null; + + //Find available clone suffix id + for (int i = 0; i < MaxCloneProjectCount; i++) + { + string originalProjectPath = ClonesManager.GetCurrentProject().projectPath; + string possibleCloneProjectPath = originalProjectPath + ClonesManager.CloneNameSuffix + "_" + i; + + if (!Directory.Exists(possibleCloneProjectPath)) + { + cloneProjectPath = possibleCloneProjectPath; + break; + } + } + + if (string.IsNullOrEmpty(cloneProjectPath)) + { + Debug.LogError("The number of cloned projects has reach its limit. Limit: " + MaxCloneProjectCount); + return null; + } + + Project cloneProject = new Project(cloneProjectPath); + + Debug.Log("Start cloning project, original project: " + sourceProject + ", clone project: " + cloneProject); + + ClonesManager.CreateProjectFolder(cloneProject); + + //Copy Folders + Debug.Log("Library copy: " + cloneProject.libraryPath); + ClonesManager.CopyDirectoryWithProgressBar(sourceProject.libraryPath, cloneProject.libraryPath, + "Cloning Project Library '" + sourceProject.name + "'. "); + Debug.Log("Packages copy: " + cloneProject.libraryPath); + ClonesManager.CopyDirectoryWithProgressBar(sourceProject.packagesPath, cloneProject.packagesPath, + "Cloning Project Packages '" + sourceProject.name + "'. "); + + + //Link Folders + ClonesManager.LinkFolders(sourceProject.assetPath, cloneProject.assetPath); + ClonesManager.LinkFolders(sourceProject.projectSettingsPath, cloneProject.projectSettingsPath); + ClonesManager.LinkFolders(sourceProject.autoBuildPath, cloneProject.autoBuildPath); + ClonesManager.LinkFolders(sourceProject.localPackages, cloneProject.localPackages); + + //Optional Link Folders + var optionalLinkPaths = Preferences.OptionalSymbolicLinkFolders.GetStoredValue(); + var projectSettings = ParrelSyncProjectSettings.GetSerializedSettings(); + var projectSettingsProperty = projectSettings.FindProperty("m_OptionalSymbolicLinkFolders"); + if (projectSettingsProperty is { isArray: true, arrayElementType: "string" }) + { + for (var i = 0; i < projectSettingsProperty.arraySize; ++i) + { + optionalLinkPaths.Add(projectSettingsProperty.GetArrayElementAtIndex(i).stringValue); + } + } + foreach (var path in optionalLinkPaths) + { + var sourceOptionalPath = sourceProjectPath + path; + var cloneOptionalPath = cloneProjectPath + path; + LinkFolders(sourceOptionalPath, cloneOptionalPath); + } + + ClonesManager.RegisterClone(cloneProject); + + return cloneProject; + } + + /// + /// Registers a clone by placing an identifying ".clone" file in its root directory. + /// + /// + private static void RegisterClone(Project cloneProject) + { + /// Add clone identifier file. + string identifierFile = Path.Combine(cloneProject.projectPath, ClonesManager.CloneFileName); + File.Create(identifierFile).Dispose(); + + //Add argument file with default argument + string argumentFilePath = Path.Combine(cloneProject.projectPath, ClonesManager.ArgumentFileName); + File.WriteAllText(argumentFilePath, DefaultArgument, System.Text.Encoding.UTF8); + + /// Add collabignore.txt to stop the clone from messing with Unity Collaborate if it's enabled. Just in case. + string collabignoreFile = Path.Combine(cloneProject.projectPath, "collabignore.txt"); + File.WriteAllText(collabignoreFile, "*"); /// Make it ignore ALL files in the clone. + } + + /// + /// Opens a project located at the given path (if one exists). + /// + /// + public static void OpenProject(string projectPath) + { + if (!Directory.Exists(projectPath)) + { + Debug.LogError("Cannot open the project - provided folder (" + projectPath + ") does not exist."); + return; + } + + if (projectPath == ClonesManager.GetCurrentProjectPath()) + { + Debug.LogError("Cannot open the project - it is already open."); + return; + } + + //Validate (and update if needed) the "Packages" folder before opening clone project to ensure the clone project will have the + //same "compiling environment" as the original project + ValidateCopiedFoldersIntegrity.ValidateFolder(projectPath, GetOriginalProjectPath(), "Packages"); + + string fileName = GetApplicationPath(); + string args = "-projectPath \"" + projectPath + "\""; + Debug.Log("Opening project \"" + fileName + " " + args + "\""); + ClonesManager.StartHiddenConsoleProcess(fileName, args); + } + + private static string GetApplicationPath() + { + switch (Application.platform) + { + case RuntimePlatform.WindowsEditor: + return EditorApplication.applicationPath; + case RuntimePlatform.OSXEditor: + return EditorApplication.applicationPath + "/Contents/MacOS/Unity"; + case RuntimePlatform.LinuxEditor: + return EditorApplication.applicationPath; + default: + throw new System.NotImplementedException("Platform has not supported yet ;("); + } + } + + /// + /// Is this project being opened by an Unity editor? + /// + /// + /// + public static bool IsCloneProjectRunning(string projectPath) + { + + //Determine whether it is opened in another instance by checking the UnityLockFile + string UnityLockFilePath = new string[] { projectPath, "Temp", "UnityLockfile" } + .Aggregate(Path.Combine); + + switch (Application.platform) + { + case (RuntimePlatform.WindowsEditor): + //Windows editor will lock "UnityLockfile" file when project is being opened. + //Sometime, for instance: windows editor crash, the "UnityLockfile" will not be deleted even the project + //isn't being opened, so a check to the "UnityLockfile" lock status may be necessary. + if (Preferences.AlsoCheckUnityLockFileStaPref.Value) + return File.Exists(UnityLockFilePath) && FileUtilities.IsFileLocked(UnityLockFilePath); + else + return File.Exists(UnityLockFilePath); + case (RuntimePlatform.OSXEditor): + //Mac editor won't lock "UnityLockfile" file when project is being opened + return File.Exists(UnityLockFilePath); + case (RuntimePlatform.LinuxEditor): + return File.Exists(UnityLockFilePath); + default: + throw new System.NotImplementedException("IsCloneProjectRunning: Unsupport Platfrom: " + Application.platform); + } + } + + /// + /// Deletes the clone of the currently open project, if such exists. + /// + public static void DeleteClone(string cloneProjectPath) + { + /// Clone won't be able to delete itself. + if (ClonesManager.IsClone()) return; + + ///Extra precautions. + if (cloneProjectPath == string.Empty) return; + if (cloneProjectPath == ClonesManager.GetOriginalProjectPath()) return; + + //Check what OS is + string identifierFile; + string args; + switch (Application.platform) + { + case (RuntimePlatform.WindowsEditor): + Debug.Log("Attempting to delete folder \"" + cloneProjectPath + "\""); + + //The argument file will be deleted first at the beginning of the project deletion process + //to prevent any further reading and writing to it(There's a File.Exist() check at the (file)editor windows.) + //If there's any file in the directory being write/read during the deletion process, the directory can't be fully removed. + identifierFile = Path.Combine(cloneProjectPath, ClonesManager.ArgumentFileName); + File.Delete(identifierFile); + + args = "/c " + @"rmdir /s/q " + string.Format("\"{0}\"", cloneProjectPath); + StartHiddenConsoleProcess("cmd.exe", args); + + break; + case (RuntimePlatform.OSXEditor): + Debug.Log("Attempting to delete folder \"" + cloneProjectPath + "\""); + + //The argument file will be deleted first at the beginning of the project deletion process + //to prevent any further reading and writing to it(There's a File.Exist() check at the (file)editor windows.) + //If there's any file in the directory being write/read during the deletion process, the directory can't be fully removed. + identifierFile = Path.Combine(cloneProjectPath, ClonesManager.ArgumentFileName); + File.Delete(identifierFile); + + FileUtil.DeleteFileOrDirectory(cloneProjectPath); + + break; + case (RuntimePlatform.LinuxEditor): + Debug.Log("Attempting to delete folder \"" + cloneProjectPath + "\""); + identifierFile = Path.Combine(cloneProjectPath, ClonesManager.ArgumentFileName); + File.Delete(identifierFile); + + FileUtil.DeleteFileOrDirectory(cloneProjectPath); + + break; + default: + Debug.LogWarning("Not in a known editor. Where are you!?"); + break; + } + } + + #endregion + + #region Creating project folders + + /// + /// Creates an empty folder using data in the given Project object + /// + /// + public static void CreateProjectFolder(Project project) + { + string path = project.projectPath; + Debug.Log("Creating new empty folder at: " + path); + Directory.CreateDirectory(path); + } + + /// + /// Copies the full contents of the unity library. We want to do this to avoid the lengthy re-serialization of the whole project when it opens up the clone. + /// + /// + /// + [System.Obsolete] + public static void CopyLibraryFolder(Project sourceProject, Project destinationProject) + { + if (Directory.Exists(destinationProject.libraryPath)) + { + Debug.LogWarning("Library copy: destination path already exists! "); + return; + } + + Debug.Log("Library copy: " + destinationProject.libraryPath); + ClonesManager.CopyDirectoryWithProgressBar(sourceProject.libraryPath, destinationProject.libraryPath, + "Cloning project '" + sourceProject.name + "'. "); + } + + #endregion + + #region Creating symlinks + + /// + /// Creates a symlink between destinationPath and sourcePath (Mac version). + /// + /// + /// + private static void CreateLinkMac(string sourcePath, string destinationPath) + { + sourcePath = sourcePath.Replace(" ", "\\ "); + destinationPath = destinationPath.Replace(" ", "\\ "); + var command = string.Format("ln -s {0} {1}", sourcePath, destinationPath); + + Debug.Log("Mac hard link " + command); + + ClonesManager.ExecuteBashCommand(command); + } + + /// + /// Creates a symlink between destinationPath and sourcePath (Linux version). + /// + /// + /// + private static void CreateLinkLinux(string sourcePath, string destinationPath) + { + sourcePath = sourcePath.Replace(" ", "\\ "); + destinationPath = destinationPath.Replace(" ", "\\ "); + var command = string.Format("ln -s {0} {1}", sourcePath, destinationPath); + + Debug.Log("Linux Symlink " + command); + + ClonesManager.ExecuteBashCommand(command); + } + + /// + /// Creates a symlink between destinationPath and sourcePath (Windows version). + /// + /// + /// + private static void CreateLinkWin(string sourcePath, string destinationPath) + { + string cmd = "/C mklink /J " + string.Format("\"{0}\" \"{1}\"", destinationPath, sourcePath); + Debug.Log("Windows junction: " + cmd); + ClonesManager.StartHiddenConsoleProcess("cmd.exe", cmd); + } + + //TODO(?) avoid terminal calls and use proper api stuff. See below for windows! + ////https://docs.microsoft.com/en-us/windows/desktop/api/ioapiset/nf-ioapiset-deviceiocontrol + //[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + //private static extern bool DeviceIoControl(System.IntPtr hDevice, uint dwIoControlCode, + // System.IntPtr InBuffer, int nInBufferSize, + // System.IntPtr OutBuffer, int nOutBufferSize, + // out int pBytesReturned, System.IntPtr lpOverlapped); + + /// + /// Create a link / junction from the original project to it's clone. + /// + /// + /// + public static void LinkFolders(string sourcePath, string destinationPath) + { + if ((Directory.Exists(destinationPath) == false) && (Directory.Exists(sourcePath) == true)) + { + switch (Application.platform) + { + case (RuntimePlatform.WindowsEditor): + CreateLinkWin(sourcePath, destinationPath); + break; + case (RuntimePlatform.OSXEditor): + CreateLinkMac(sourcePath, destinationPath); + break; + case (RuntimePlatform.LinuxEditor): + CreateLinkLinux(sourcePath, destinationPath); + break; + default: + Debug.LogWarning("Not in a known editor. Application.platform: " + Application.platform); + break; + } + } + else + { + Debug.LogWarning("Skipping Asset link, it already exists: " + destinationPath); + } + } + + #endregion + + #region Utility methods + + private static bool? isCloneFileExistCache = null; + + /// + /// Returns true if the project currently open in Unity Editor is a clone. + /// + /// + public static bool IsClone() + { + if (isCloneFileExistCache == null) + { + /// The project is a clone if its root directory contains an empty file named ".clone". + string cloneFilePath = Path.Combine(ClonesManager.GetCurrentProjectPath(), ClonesManager.CloneFileName); + isCloneFileExistCache = File.Exists(cloneFilePath); + } + + return (bool)isCloneFileExistCache; + } + + /// + /// Get the path to the current unityEditor project folder's info + /// + /// + public static string GetCurrentProjectPath() + { + return Application.dataPath.Replace("/Assets", ""); + } + + /// + /// Return a project object that describes all the paths we need to clone it. + /// + /// + public static Project GetCurrentProject() + { + string pathString = ClonesManager.GetCurrentProjectPath(); + return new Project(pathString); + } + + /// + /// Get the argument of this clone project. + /// If this is the original project, will return an empty string. + /// + /// + public static string GetArgument() + { + string argument = ""; + if (IsClone()) + { + string argumentFilePath = Path.Combine(GetCurrentProjectPath(), ClonesManager.ArgumentFileName); + if (File.Exists(argumentFilePath)) + { + argument = File.ReadAllText(argumentFilePath, System.Text.Encoding.UTF8); + } + } + + return argument; + } + + /// + /// Returns the path to the original project. + /// If currently open project is the original, returns its own path. + /// If the original project folder cannot be found, retuns an empty string. + /// + /// + public static string GetOriginalProjectPath() + { + if (IsClone()) + { + /// If this is a clone... + /// Original project path can be deduced by removing the suffix from the clone's path. + string cloneProjectPath = ClonesManager.GetCurrentProject().projectPath; + + int index = cloneProjectPath.LastIndexOf(ClonesManager.CloneNameSuffix); + if (index > 0) + { + string originalProjectPath = cloneProjectPath.Substring(0, index); + if (Directory.Exists(originalProjectPath)) return originalProjectPath; + } + + return string.Empty; + } + else + { + /// If this is the original, we return its own path. + return ClonesManager.GetCurrentProjectPath(); + } + } + + /// + /// Returns all clone projects path. + /// + /// + public static List GetCloneProjectsPath() + { + List projectsPath = new List(); + for (int i = 0; i < MaxCloneProjectCount; i++) + { + string originalProjectPath = ClonesManager.GetCurrentProject().projectPath; + string cloneProjectPath = originalProjectPath + ClonesManager.CloneNameSuffix + "_" + i; + + if (Directory.Exists(cloneProjectPath)) + projectsPath.Add(cloneProjectPath); + } + + return projectsPath; + } + + /// + /// Copies directory located at sourcePath to destinationPath. Displays a progress bar. + /// + /// Directory to be copied. + /// Destination directory (created automatically if needed). + /// Optional string added to the beginning of the progress bar window header. + public static void CopyDirectoryWithProgressBar(string sourcePath, string destinationPath, + string progressBarPrefix = "") + { + var source = new DirectoryInfo(sourcePath); + var destination = new DirectoryInfo(destinationPath); + + long totalBytes = 0; + long copiedBytes = 0; + + ClonesManager.CopyDirectoryWithProgressBarRecursive(source, destination, ref totalBytes, ref copiedBytes, + progressBarPrefix); + EditorUtility.ClearProgressBar(); + } + + /// + /// Copies directory located at sourcePath to destinationPath. Displays a progress bar. + /// Same as the previous method, but uses recursion to copy all nested folders as well. + /// + /// Directory to be copied. + /// Destination directory (created automatically if needed). + /// Total bytes to be copied. Calculated automatically, initialize at 0. + /// To track already copied bytes. Calculated automatically, initialize at 0. + /// Optional string added to the beginning of the progress bar window header. + private static void CopyDirectoryWithProgressBarRecursive(DirectoryInfo source, DirectoryInfo destination, + ref long totalBytes, ref long copiedBytes, string progressBarPrefix = "") + { + /// Directory cannot be copied into itself. + if (source.FullName.ToLower() == destination.FullName.ToLower()) + { + Debug.LogError("Cannot copy directory into itself."); + return; + } + + /// Calculate total bytes, if required. + if (totalBytes == 0) + { + totalBytes = ClonesManager.GetDirectorySize(source, true, progressBarPrefix); + } + + /// Create destination directory, if required. + if (!Directory.Exists(destination.FullName)) + { + Directory.CreateDirectory(destination.FullName); + } + + /// Copy all files from the source. + foreach (FileInfo file in source.GetFiles()) + { + // Ensure file exists before continuing. + if (!file.Exists) + { + continue; + } + + try + { + file.CopyTo(Path.Combine(destination.ToString(), file.Name), true); + } + catch (IOException) + { + /// Some files may throw IOException if they are currently open in Unity editor. + /// Just ignore them in such case. + } + + /// Account the copied file size. + copiedBytes += file.Length; + + /// Display the progress bar. + float progress = (float)copiedBytes / (float)totalBytes; + bool cancelCopy = EditorUtility.DisplayCancelableProgressBar( + progressBarPrefix + "Copying '" + source.FullName + "' to '" + destination.FullName + "'...", + "(" + (progress * 100f).ToString("F2") + "%) Copying file '" + file.Name + "'...", + progress); + if (cancelCopy) return; + } + + /// Copy all nested directories from the source. + foreach (DirectoryInfo sourceNestedDir in source.GetDirectories()) + { + DirectoryInfo nextDestingationNestedDir = destination.CreateSubdirectory(sourceNestedDir.Name); + ClonesManager.CopyDirectoryWithProgressBarRecursive(sourceNestedDir, nextDestingationNestedDir, + ref totalBytes, ref copiedBytes, progressBarPrefix); + } + } + + /// + /// Calculates the size of the given directory. Displays a progress bar. + /// + /// Directory, which size has to be calculated. + /// If true, size will include all nested directories. + /// Optional string added to the beginning of the progress bar window header. + /// Size of the directory in bytes. + private static long GetDirectorySize(DirectoryInfo directory, bool includeNested = false, + string progressBarPrefix = "") + { + EditorUtility.DisplayProgressBar(progressBarPrefix + "Calculating size of directories...", + "Scanning '" + directory.FullName + "'...", 0f); + + /// Calculate size of all files in directory. + long filesSize = directory.GetFiles().Sum((FileInfo file) => file.Exists ? file.Length : 0); + + /// Calculate size of all nested directories. + long directoriesSize = 0; + if (includeNested) + { + IEnumerable nestedDirectories = directory.GetDirectories(); + foreach (DirectoryInfo nestedDir in nestedDirectories) + { + directoriesSize += ClonesManager.GetDirectorySize(nestedDir, true, progressBarPrefix); + } + } + + return filesSize + directoriesSize; + } + + /// + /// Starts process in the system console, taking the given fileName and args. + /// + /// + /// + private static void StartHiddenConsoleProcess(string fileName, string args) + { + System.Diagnostics.Process.Start(fileName, args); + } + + /// + /// Thanks to https://github.com/karl-/unity-symlink-utility/blob/master/SymlinkUtility.cs + /// + /// + private static void ExecuteBashCommand(string command) + { + command = command.Replace("\"", "\"\""); + + var proc = new Process() + { + StartInfo = new ProcessStartInfo + { + FileName = "/bin/bash", + Arguments = "-c \"" + command + "\"", + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + CreateNoWindow = true + } + }; + + using (proc) + { + proc.Start(); + proc.WaitForExit(); + + if (!proc.StandardError.EndOfStream) + { + UnityEngine.Debug.LogError(proc.StandardError.ReadToEnd()); + } + } + } + + public static void OpenProjectInFileExplorer(string path) + { + System.Diagnostics.Process.Start(@path); + } + #endregion + } +} diff --git a/Assets/ParrelSync/Editor/ClonesManager.cs.meta b/Assets/ParrelSync/Editor/ClonesManager.cs.meta new file mode 100644 index 0000000..5800cf8 --- /dev/null +++ b/Assets/ParrelSync/Editor/ClonesManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6148e48ed6b61d748b187d06d3687b83 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/Editor/ClonesManagerWindow.cs b/Assets/ParrelSync/Editor/ClonesManagerWindow.cs new file mode 100644 index 0000000..ad9619e --- /dev/null +++ b/Assets/ParrelSync/Editor/ClonesManagerWindow.cs @@ -0,0 +1,198 @@ +using UnityEngine; +using UnityEditor; +using System.IO; + +namespace ParrelSync +{ + /// + ///Clones manager Unity editor window + /// + public class ClonesManagerWindow : EditorWindow + { + /// + /// Returns true if project clone exists. + /// + public bool isCloneCreated + { + get { return ClonesManager.GetCloneProjectsPath().Count >= 1; } + } + + [MenuItem("ParrelSync/Clones Manager", priority = 0)] + private static void InitWindow() + { + ClonesManagerWindow window = (ClonesManagerWindow)EditorWindow.GetWindow(typeof(ClonesManagerWindow)); + window.titleContent = new GUIContent("Clones Manager"); + window.Show(); + } + + /// + /// For storing the scroll position of clones list + /// + Vector2 clonesScrollPos; + + private void OnGUI() + { + /// If it is a clone project... + if (ClonesManager.IsClone()) + { + //Find out the original project name and show the help box + string originalProjectPath = ClonesManager.GetOriginalProjectPath(); + if (originalProjectPath == string.Empty) + { + /// If original project cannot be found, display warning message. + EditorGUILayout.HelpBox( + "This project is a clone, but the link to the original seems lost.\nYou have to manually open the original and create a new clone instead of this one.\n", + MessageType.Warning); + } + else + { + /// If original project is present, display some usage info. + EditorGUILayout.HelpBox( + "This project is a clone of the project '" + Path.GetFileName(originalProjectPath) + "'.\nIf you want to make changes the project files or manage clones, please open the original project through Unity Hub.", + MessageType.Info); + } + + //Clone project custom argument. + GUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Arguments", GUILayout.Width(70)); + if (GUILayout.Button("?", GUILayout.Width(20))) + { + Application.OpenURL(ExternalLinks.CustomArgumentHelpLink); + } + GUILayout.EndHorizontal(); + + string argumentFilePath = Path.Combine(ClonesManager.GetCurrentProjectPath(), ClonesManager.ArgumentFileName); + //Need to be careful with file reading / writing since it will effect the deletion of + // the clone project(The directory won't be fully deleted if there's still file inside being read or write). + //The argument file will be deleted first at the beginning of the project deletion process + //to prevent any further being read and write. + //Will need to take some extra cautious if want to change the design of how file editing is handled. + if (File.Exists(argumentFilePath)) + { + string argument = File.ReadAllText(argumentFilePath, System.Text.Encoding.UTF8); + string argumentTextAreaInput = EditorGUILayout.TextArea(argument, + GUILayout.Height(50), + GUILayout.MaxWidth(300) + ); + File.WriteAllText(argumentFilePath, argumentTextAreaInput, System.Text.Encoding.UTF8); + } + else + { + EditorGUILayout.LabelField("No argument file found."); + } + } + else// If it is an original project... + { + if (isCloneCreated) + { + GUILayout.BeginVertical("HelpBox"); + GUILayout.Label("Clones of this Project"); + + //List all clones + clonesScrollPos = + EditorGUILayout.BeginScrollView(clonesScrollPos); + var cloneProjectsPath = ClonesManager.GetCloneProjectsPath(); + for (int i = 0; i < cloneProjectsPath.Count; i++) + { + + GUILayout.BeginVertical("GroupBox"); + string cloneProjectPath = cloneProjectsPath[i]; + + bool isOpenInAnotherInstance = ClonesManager.IsCloneProjectRunning(cloneProjectPath); + + if (isOpenInAnotherInstance == true) + EditorGUILayout.LabelField("Clone " + i + " (Running)", EditorStyles.boldLabel); + else + EditorGUILayout.LabelField("Clone " + i); + + + GUILayout.BeginHorizontal(); + EditorGUILayout.TextField("Clone project path", cloneProjectPath, EditorStyles.textField); + if (GUILayout.Button("View Folder", GUILayout.Width(80))) + { + ClonesManager.OpenProjectInFileExplorer(cloneProjectPath); + } + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(); + EditorGUILayout.LabelField("Arguments", GUILayout.Width(70)); + if (GUILayout.Button("?", GUILayout.Width(20))) + { + Application.OpenURL(ExternalLinks.CustomArgumentHelpLink); + } + GUILayout.EndHorizontal(); + + string argumentFilePath = Path.Combine(cloneProjectPath, ClonesManager.ArgumentFileName); + //Need to be careful with file reading/writing since it will effect the deletion of + //the clone project(The directory won't be fully deleted if there's still file inside being read or write). + //The argument file will be deleted first at the beginning of the project deletion process + //to prevent any further being read and write. + //Will need to take some extra cautious if want to change the design of how file editing is handled. + if (File.Exists(argumentFilePath)) + { + string argument = File.ReadAllText(argumentFilePath, System.Text.Encoding.UTF8); + string argumentTextAreaInput = EditorGUILayout.TextArea(argument, + GUILayout.Height(50), + GUILayout.MaxWidth(300) + ); + File.WriteAllText(argumentFilePath, argumentTextAreaInput, System.Text.Encoding.UTF8); + } + else + { + EditorGUILayout.LabelField("No argument file found."); + } + + EditorGUILayout.Space(); + EditorGUILayout.Space(); + EditorGUILayout.Space(); + + + EditorGUI.BeginDisabledGroup(isOpenInAnotherInstance); + + if (GUILayout.Button("Open in New Editor")) + { + ClonesManager.OpenProject(cloneProjectPath); + } + + GUILayout.BeginHorizontal(); + if (GUILayout.Button("Delete")) + { + bool delete = EditorUtility.DisplayDialog( + "Delete the clone?", + "Are you sure you want to delete the clone project '" + ClonesManager.GetCurrentProject().name + "_clone'?", + "Delete", + "Cancel"); + if (delete) + { + ClonesManager.DeleteClone(cloneProjectPath); + } + } + + GUILayout.EndHorizontal(); + EditorGUI.EndDisabledGroup(); + GUILayout.EndVertical(); + + } + EditorGUILayout.EndScrollView(); + + if (GUILayout.Button("Add new clone")) + { + ClonesManager.CreateCloneFromCurrent(); + } + + GUILayout.EndVertical(); + GUILayout.FlexibleSpace(); + } + else + { + /// If no clone created yet, we must create it. + EditorGUILayout.HelpBox("No project clones found. Create a new one!", MessageType.Info); + if (GUILayout.Button("Create new clone")) + { + ClonesManager.CreateCloneFromCurrent(); + } + } + } + } + } +} diff --git a/Assets/ParrelSync/Editor/ClonesManagerWindow.cs.meta b/Assets/ParrelSync/Editor/ClonesManagerWindow.cs.meta new file mode 100644 index 0000000..ac75a04 --- /dev/null +++ b/Assets/ParrelSync/Editor/ClonesManagerWindow.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a041d83486c20b84bbf5077ddfbbca37 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/Editor/ExternalLinks.cs b/Assets/ParrelSync/Editor/ExternalLinks.cs new file mode 100644 index 0000000..84809bc --- /dev/null +++ b/Assets/ParrelSync/Editor/ExternalLinks.cs @@ -0,0 +1,13 @@ +namespace ParrelSync +{ + public class ExternalLinks + { + public const string RemoteVersionURL = "https://raw.githubusercontent.com/VeriorPies/ParrelSync/master/VERSION.txt"; + public const string Releases = "https://github.com/VeriorPies/ParrelSync/releases"; + public const string CustomArgumentHelpLink = "https://github.com/VeriorPies/ParrelSync/wiki/Argument"; + + public const string GitHubHome = "https://github.com/VeriorPies/ParrelSync/"; + public const string GitHubIssue = "https://github.com/VeriorPies/ParrelSync/issues"; + public const string FAQ = "https://github.com/VeriorPies/ParrelSync/wiki/Troubleshooting-&-FAQs"; + } +} \ No newline at end of file diff --git a/Assets/ParrelSync/Editor/ExternalLinks.cs.meta b/Assets/ParrelSync/Editor/ExternalLinks.cs.meta new file mode 100644 index 0000000..c238b5c --- /dev/null +++ b/Assets/ParrelSync/Editor/ExternalLinks.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 65daf17fbe5101b41977305639f30c65 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/Editor/FileUtilities.cs b/Assets/ParrelSync/Editor/FileUtilities.cs new file mode 100644 index 0000000..999ee02 --- /dev/null +++ b/Assets/ParrelSync/Editor/FileUtilities.cs @@ -0,0 +1,31 @@ +using System.IO; +using UnityEngine; + +namespace ParrelSync +{ + public class FileUtilities + { + public static bool IsFileLocked(string path) + { + FileInfo file = new FileInfo(path); + try + { + using (FileStream stream = file.Open(FileMode.Open, FileAccess.Read, FileShare.None)) + { + stream.Close(); + } + } + catch (IOException) + { + //the file is unavailable because it is: + //still being written to + //or being processed by another thread + //or does not exist (has already been processed) + return true; + } + + //file is not locked + return false; + } + } +} \ No newline at end of file diff --git a/Assets/ParrelSync/Editor/FileUtilities.cs.meta b/Assets/ParrelSync/Editor/FileUtilities.cs.meta new file mode 100644 index 0000000..2733944 --- /dev/null +++ b/Assets/ParrelSync/Editor/FileUtilities.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 11fdc6f78f8c965499a870ca06dca6bc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/Editor/NonCore.meta b/Assets/ParrelSync/Editor/NonCore.meta new file mode 100644 index 0000000..5b4e192 --- /dev/null +++ b/Assets/ParrelSync/Editor/NonCore.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 74a7aa389726f964ab34c52e208c2a43 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/Editor/NonCore/AskFeedbackDialog.cs b/Assets/ParrelSync/Editor/NonCore/AskFeedbackDialog.cs new file mode 100644 index 0000000..2bb988a --- /dev/null +++ b/Assets/ParrelSync/Editor/NonCore/AskFeedbackDialog.cs @@ -0,0 +1,78 @@ +namespace ParrelSync.NonCore +{ + using UnityEditor; + using UnityEngine; + + /// + /// A simple script to display feedback/star dialog after certain time of project being opened/re-compiled. + /// Will only pop-up once unless "Remind me next time" are chosen. + /// Removing this file from project wont effect any other functions. + /// + [InitializeOnLoad] + public class AskFeedbackDialog + { + const string InitializeOnLoadCountKey = "ParrelSync_InitOnLoadCount", StopShowingKey = "ParrelSync_StopShowFeedBack"; + static AskFeedbackDialog() + { + if (EditorPrefs.HasKey(StopShowingKey)) { return; } + + int InitializeOnLoadCount = EditorPrefs.GetInt(InitializeOnLoadCountKey, 0); + if (InitializeOnLoadCount > 20) + { + ShowDialog(); + } + else + { + EditorPrefs.SetInt(InitializeOnLoadCountKey, InitializeOnLoadCount + 1); + } + } + + //[MenuItem("ParrelSync/(Debug)Show AskFeedbackDialog ")] + private static void ShowDialog() + { + int option = EditorUtility.DisplayDialogComplex("Do you like " + ParrelSync.ClonesManager.ProjectName + "?", + "Do you like " + ParrelSync.ClonesManager.ProjectName + "?\n" + + "If so, please don't hesitate to star it on GitHub and contribute to the project!", + "Star on GitHub", + "Close", + "Remind me next time" + ); + + switch (option) + { + // First parameter. + case 0: + Debug.Log("AskFeedbackDialog: Star on GitHub selected"); + EditorPrefs.SetBool(StopShowingKey, true); + EditorPrefs.DeleteKey(InitializeOnLoadCountKey); + Application.OpenURL(ExternalLinks.GitHubHome); + break; + // Second parameter. + case 1: + Debug.Log("AskFeedbackDialog: Close and never show again."); + EditorPrefs.SetBool(StopShowingKey, true); + EditorPrefs.DeleteKey(InitializeOnLoadCountKey); + break; + // Third parameter. + case 2: + Debug.Log("AskFeedbackDialog: Remind me next time"); + EditorPrefs.SetInt(InitializeOnLoadCountKey, 0); + break; + default: + //Debug.Log("Close windows."); + break; + } + } + + ///// + ///// For debug purpose + ///// + //[MenuItem("ParrelSync/(Debug)Delete AskFeedbackDialog keys")] + //private static void DebugDeleteAllKeys() + //{ + // EditorPrefs.DeleteKey(InitializeOnLoadCountKey); + // EditorPrefs.DeleteKey(StopShowingKey); + // Debug.Log("AskFeedbackDialog keys deleted"); + //} + } +} \ No newline at end of file diff --git a/Assets/ParrelSync/Editor/NonCore/AskFeedbackDialog.cs.meta b/Assets/ParrelSync/Editor/NonCore/AskFeedbackDialog.cs.meta new file mode 100644 index 0000000..20a2a0b --- /dev/null +++ b/Assets/ParrelSync/Editor/NonCore/AskFeedbackDialog.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 894412a5b602e6c4ba2cf2d01f4f92b5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/Editor/NonCore/OtherMenuItem.cs b/Assets/ParrelSync/Editor/NonCore/OtherMenuItem.cs new file mode 100644 index 0000000..0f42af9 --- /dev/null +++ b/Assets/ParrelSync/Editor/NonCore/OtherMenuItem.cs @@ -0,0 +1,26 @@ +namespace ParrelSync.NonCore +{ + using UnityEditor; + using UnityEngine; + + public class OtherMenuItem + { + [MenuItem("ParrelSync/GitHub/View this project on GitHub", priority = 10)] + private static void OpenGitHub() + { + Application.OpenURL(ExternalLinks.GitHubHome); + } + + [MenuItem("ParrelSync/GitHub/View FAQ", priority = 11)] + private static void OpenFAQ() + { + Application.OpenURL(ExternalLinks.FAQ); + } + + [MenuItem("ParrelSync/GitHub/View Issues", priority = 12)] + private static void OpenGitHubIssues() + { + Application.OpenURL(ExternalLinks.GitHubIssue); + } + } +} \ No newline at end of file diff --git a/Assets/ParrelSync/Editor/NonCore/OtherMenuItem.cs.meta b/Assets/ParrelSync/Editor/NonCore/OtherMenuItem.cs.meta new file mode 100644 index 0000000..563d7a2 --- /dev/null +++ b/Assets/ParrelSync/Editor/NonCore/OtherMenuItem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7191fa4bfa12ae749b27f73ed292eaf1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/Editor/ParrelSyncProjectSettings.cs b/Assets/ParrelSync/Editor/ParrelSyncProjectSettings.cs new file mode 100644 index 0000000..576d41c --- /dev/null +++ b/Assets/ParrelSync/Editor/ParrelSyncProjectSettings.cs @@ -0,0 +1,140 @@ +using System.Collections.Generic; +using System.IO; +using UnityEditor; +using UnityEngine; +using UnityEngine.UIElements; + +namespace ParrelSync +{ + // With ScriptableObject derived classes, .cs and .asset filenames MUST be identical + public class ParrelSyncProjectSettings : ScriptableObject + { + private const string ParrelSyncScriptableObjectsDirectory = "Assets/Plugins/ParrelSync/ScriptableObjects"; + private const string ParrelSyncSettingsPath = ParrelSyncScriptableObjectsDirectory + "/" + + nameof(ParrelSyncProjectSettings) + ".asset"; + + [SerializeField] + [HideInInspector] + private List m_OptionalSymbolicLinkFolders; + public const string NameOfOptionalSymbolicLinkFolders = nameof(m_OptionalSymbolicLinkFolders); + + private static ParrelSyncProjectSettings GetOrCreateSettings() + { + ParrelSyncProjectSettings projectSettings; + if (File.Exists(ParrelSyncSettingsPath)) + { + projectSettings = AssetDatabase.LoadAssetAtPath(ParrelSyncSettingsPath); + + if (projectSettings == null) + Debug.LogError("File Exists, but failed to load: " + ParrelSyncSettingsPath); + + return projectSettings; + } + + projectSettings = CreateInstance(); + projectSettings.m_OptionalSymbolicLinkFolders = new List(); + if (!Directory.Exists(ParrelSyncScriptableObjectsDirectory)) + { + Directory.CreateDirectory(ParrelSyncScriptableObjectsDirectory); + } + AssetDatabase.CreateAsset(projectSettings, ParrelSyncSettingsPath); + AssetDatabase.SaveAssets(); + return projectSettings; + } + + public static SerializedObject GetSerializedSettings() + { + return new SerializedObject(GetOrCreateSettings()); + } + } + + public class ParrelSyncSettingsProvider : SettingsProvider + { + private const string MenuLocationInProjectSettings = "Project/ParrelSync"; + + private SerializedObject _parrelSyncProjectSettings; + + private class Styles + { + public static readonly GUIContent SymlinkSectionHeading = new GUIContent("Optional Folders to Symbolically Link"); + } + + private ParrelSyncSettingsProvider(string path, SettingsScope scope = SettingsScope.User) + : base(path, scope) + { + } + + public override void OnActivate(string searchContext, VisualElement rootElement) + { + // This function is called when the user clicks on the ParrelSyncSettings element in the Settings window. + _parrelSyncProjectSettings = ParrelSyncProjectSettings.GetSerializedSettings(); + } + + public override void OnGUI(string searchContext) + { + var property = _parrelSyncProjectSettings.FindProperty(ParrelSyncProjectSettings.NameOfOptionalSymbolicLinkFolders); + if (property is null || !property.isArray || property.arrayElementType != "string") + return; + + var optionalFolderPaths = new List(property.arraySize); + for (var i = 0; i < property.arraySize; ++i) + { + optionalFolderPaths.Add(property.GetArrayElementAtIndex(i).stringValue); + } + optionalFolderPaths.Add(""); + + GUILayout.BeginVertical("GroupBox"); + GUILayout.Label(Styles.SymlinkSectionHeading); + GUILayout.Space(5); + var projectPath = ClonesManager.GetCurrentProjectPath(); + var optionalFolderPathsIsDirty = false; + for (var i = 0; i < optionalFolderPaths.Count; ++i) + { + GUILayout.BeginHorizontal(); + EditorGUILayout.LabelField(optionalFolderPaths[i], EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); + if (GUILayout.Button("Select", GUILayout.Width(60))) + { + var result = EditorUtility.OpenFolderPanel("Select Folder to Symbolically Link...", "", ""); + if (result.Contains(projectPath)) + { + optionalFolderPaths[i] = result.Replace(projectPath, ""); + optionalFolderPathsIsDirty = true; + } + else if (result != "") + { + Debug.LogWarning("Symbolic Link folder must be within the project directory"); + } + } + if (GUILayout.Button("Clear", GUILayout.Width(60))) + { + optionalFolderPaths[i] = ""; + optionalFolderPathsIsDirty = true; + } + GUILayout.EndHorizontal(); + } + GUILayout.EndVertical(); + + if (!optionalFolderPathsIsDirty) + return; + + optionalFolderPaths.RemoveAll(str => str == ""); + property.arraySize = optionalFolderPaths.Count; + for (var i = 0; i < property.arraySize; ++i) + { + property.GetArrayElementAtIndex(i).stringValue = optionalFolderPaths[i]; + } + _parrelSyncProjectSettings.ApplyModifiedProperties(); + AssetDatabase.SaveAssets(); + } + + // Register the SettingsProvider + [SettingsProvider] + public static SettingsProvider CreateParrelSyncSettingsProvider() + { + return new ParrelSyncSettingsProvider(MenuLocationInProjectSettings, SettingsScope.Project) + { + keywords = GetSearchKeywordsFromGUIContentProperties() + }; + } + } +} \ No newline at end of file diff --git a/Assets/ParrelSync/Editor/ParrelSyncProjectSettings.cs.meta b/Assets/ParrelSync/Editor/ParrelSyncProjectSettings.cs.meta new file mode 100644 index 0000000..95506e4 --- /dev/null +++ b/Assets/ParrelSync/Editor/ParrelSyncProjectSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c0011418c9d75434988a06b6df93b283 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/Editor/Preferences.cs b/Assets/ParrelSync/Editor/Preferences.cs new file mode 100644 index 0000000..095a470 --- /dev/null +++ b/Assets/ParrelSync/Editor/Preferences.cs @@ -0,0 +1,215 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using UnityEditor; + +namespace ParrelSync +{ + /// + /// To add value caching for functions + /// + public class BoolPreference + { + public string key { get; private set; } + public bool defaultValue { get; private set; } + public BoolPreference(string key, bool defaultValue) + { + this.key = key; + this.defaultValue = defaultValue; + } + + private bool? valueCache = null; + + public bool Value + { + get + { + if (valueCache == null) + valueCache = EditorPrefs.GetBool(key, defaultValue); + + return (bool)valueCache; + } + set + { + if (valueCache == value) + return; + + EditorPrefs.SetBool(key, value); + valueCache = value; + Debug.Log("Editor preference updated. key: " + key + ", value: " + value); + } + } + + public void ClearValue() + { + EditorPrefs.DeleteKey(key); + valueCache = null; + } + } + + + /// + /// To add value caching for functions + /// + public class ListOfStringsPreference + { + private static string serializationToken = "|||"; + public string Key { get; private set; } + public ListOfStringsPreference(string key) + { + Key = key; + } + public List GetStoredValue() + { + return this.Deserialize(EditorPrefs.GetString(Key)); + } + public void SetStoredValue(List strings) + { + EditorPrefs.SetString(Key, this.Serialize(strings)); + } + public void ClearStoredValue() + { + EditorPrefs.DeleteKey(Key); + } + public string Serialize(List data) + { + string result = string.Empty; + foreach (var item in data) + { + if (item.Contains(serializationToken)) + { + Debug.LogError("Unable to serialize this value ["+item+"], it contains the serialization token ["+serializationToken+"]"); + continue; + } + + result += item + serializationToken; + } + return result; + } + public List Deserialize(string data) + { + return data.Split(serializationToken).ToList(); + } + } + public class Preferences : EditorWindow + { + [MenuItem("ParrelSync/Preferences", priority = 1)] + private static void InitWindow() + { + Preferences window = (Preferences)EditorWindow.GetWindow(typeof(Preferences)); + window.titleContent = new GUIContent(ClonesManager.ProjectName + " Preferences"); + window.minSize = new Vector2(550, 300); + window.Show(); + } + + /// + /// Disable asset saving in clone editors? + /// + public static BoolPreference AssetModPref = new BoolPreference("ParrelSync_DisableClonesAssetSaving", true); + + /// + /// In addition of checking the existence of UnityLockFile, + /// also check is the is the UnityLockFile being opened. + /// + public static BoolPreference AlsoCheckUnityLockFileStaPref = new BoolPreference("ParrelSync_CheckUnityLockFileOpenStatus", true); + + /// + /// A list of folders to create sybolic links for, + /// useful for data that lives outside of the assets folder + /// eg. Wwise project data + /// + public static ListOfStringsPreference OptionalSymbolicLinkFolders = new ListOfStringsPreference("ParrelSync_OptionalSymbolicLinkFolders"); + + private void OnGUI() + { + if (ClonesManager.IsClone()) + { + EditorGUILayout.HelpBox( + "This is a clone project. Please use the original project editor to change preferences.", + MessageType.Info); + return; + } + + GUILayout.BeginVertical("HelpBox"); + GUILayout.Label("Preferences"); + GUILayout.BeginVertical("GroupBox"); + + AssetModPref.Value = EditorGUILayout.ToggleLeft( + new GUIContent( + "(recommended) Disable asset saving in clone editors- require re-open clone editors", + "Disable asset saving in clone editors so all assets can only be modified from the original project editor" + ), + AssetModPref.Value); + + if (Application.platform == RuntimePlatform.WindowsEditor) + { + AlsoCheckUnityLockFileStaPref.Value = EditorGUILayout.ToggleLeft( + new GUIContent( + "Also check UnityLockFile lock status while checking clone projects running status", + "Disable this can slightly increase Clones Manager window performance, but will lead to in-correct clone project running status" + + "(the Clones Manager window show the clone project is still running even it's not) if the clone editor crashed" + ), + AlsoCheckUnityLockFileStaPref.Value); + } + GUILayout.EndVertical(); + + GUILayout.BeginVertical("GroupBox"); + GUILayout.Label("Optional Folders to Symbolically Link"); + GUILayout.Space(5); + + // cache the current value + List optionalFolderPaths = OptionalSymbolicLinkFolders.GetStoredValue(); + bool optionalFolderPathsAreDirty = false; + + // append a new row if full + if (optionalFolderPaths.Last() != "") + { + optionalFolderPaths.Add(""); + } + + var projectPath = ClonesManager.GetCurrentProjectPath(); + for (int i = 0; i < optionalFolderPaths.Count; ++i) + { + GUILayout.BeginHorizontal(); + EditorGUILayout.LabelField(optionalFolderPaths[i], EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); + if (GUILayout.Button("Select Folder", GUILayout.Width(100))) + { + var result = EditorUtility.OpenFolderPanel("Select Folder to Symbolically Link...", "", ""); + if (result.Contains(projectPath)) + { + optionalFolderPaths[i] = result.Replace(projectPath,""); + optionalFolderPathsAreDirty = true; + } + else if( result != "") + { + Debug.LogWarning("Symbolic Link folder must be within the project directory"); + } + } + if (GUILayout.Button("Clear", GUILayout.Width(100))) + { + optionalFolderPaths[i] = ""; + optionalFolderPathsAreDirty = true; + } + GUILayout.EndHorizontal(); + } + + // only set the preference if the value is marked dirty + if (optionalFolderPathsAreDirty) + { + optionalFolderPaths.RemoveAll(str=> str == ""); + OptionalSymbolicLinkFolders.SetStoredValue(optionalFolderPaths); + } + + GUILayout.EndVertical(); + + if (GUILayout.Button("Reset to default")) + { + AssetModPref.ClearValue(); + AlsoCheckUnityLockFileStaPref.ClearValue(); + OptionalSymbolicLinkFolders.ClearStoredValue(); + Debug.Log("Editor preferences cleared"); + } + GUILayout.EndVertical(); + } + } +} diff --git a/Assets/ParrelSync/Editor/Preferences.cs.meta b/Assets/ParrelSync/Editor/Preferences.cs.meta new file mode 100644 index 0000000..0166f9a --- /dev/null +++ b/Assets/ParrelSync/Editor/Preferences.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 24641be1c0410a745b529e61b508679f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/Editor/Project.cs b/Assets/ParrelSync/Editor/Project.cs new file mode 100644 index 0000000..7e7c387 --- /dev/null +++ b/Assets/ParrelSync/Editor/Project.cs @@ -0,0 +1,112 @@ +using System.Collections.Generic; +using System.Linq; + +namespace ParrelSync +{ + public class Project : System.ICloneable + { + public string name; + public string projectPath; + string rootPath; + public string assetPath; + public string projectSettingsPath; + public string libraryPath; + public string packagesPath; + public string autoBuildPath; + public string localPackages; + + char[] separator = new char[1] { '/' }; + + + /// + /// Default constructor + /// + public Project() + { + + } + + + /// + /// Initialize the project object by parsing its full path returned by Unity into a bunch of individual folder names and paths. + /// + /// + public Project(string path) + { + ParsePath(path); + } + + + /// + /// Create a new object with the same settings + /// + /// + public object Clone() + { + Project newProject = new Project(); + newProject.rootPath = rootPath; + newProject.projectPath = projectPath; + newProject.assetPath = assetPath; + newProject.projectSettingsPath = projectSettingsPath; + newProject.libraryPath = libraryPath; + newProject.name = name; + newProject.separator = separator; + newProject.packagesPath = packagesPath; + newProject.autoBuildPath = autoBuildPath; + newProject.localPackages = localPackages; + + + return newProject; + } + + + /// + /// Update the project object by renaming and reparsing it. Pass in the new name of a project, and it'll update the other member variables to match. + /// + /// + public void updateNewName(string newName) + { + name = newName; + ParsePath(rootPath + "/" + name + "/Assets"); + } + + + /// + /// Debug override so we can quickly print out the project info. + /// + /// + public override string ToString() + { + string printString = name + "\n" + + rootPath + "\n" + + projectPath + "\n" + + assetPath + "\n" + + projectSettingsPath + "\n" + + packagesPath + "\n" + + autoBuildPath + "\n" + + localPackages + "\n" + + libraryPath; + return (printString); + } + + private void ParsePath(string path) + { + //Unity's Application functions return the Assets path in the Editor. + projectPath = path; + + //pop off the last part of the path for the project name, keep the rest for the root path + List pathArray = projectPath.Split(separator).ToList(); + name = pathArray.Last(); + + pathArray.RemoveAt(pathArray.Count() - 1); + rootPath = string.Join(separator[0].ToString(), pathArray.ToArray()); + + assetPath = projectPath + "/Assets"; + projectSettingsPath = projectPath + "/ProjectSettings"; + libraryPath = projectPath + "/Library"; + packagesPath = projectPath + "/Packages"; + autoBuildPath = projectPath + "/AutoBuild"; + localPackages = projectPath + "/LocalPackages"; + } + } +} \ No newline at end of file diff --git a/Assets/ParrelSync/Editor/Project.cs.meta b/Assets/ParrelSync/Editor/Project.cs.meta new file mode 100644 index 0000000..84d9855 --- /dev/null +++ b/Assets/ParrelSync/Editor/Project.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ec8d3a1577179ef44815739178cf75b4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/Editor/UpdateChecker.cs b/Assets/ParrelSync/Editor/UpdateChecker.cs new file mode 100644 index 0000000..a93895d --- /dev/null +++ b/Assets/ParrelSync/Editor/UpdateChecker.cs @@ -0,0 +1,60 @@ +using System; +using UnityEditor; +using UnityEngine; +namespace ParrelSync.Update +{ + /// + /// A simple update checker + /// + public class UpdateChecker + { + //const string LocalVersionFilePath = "Assets/ParrelSync/VERSION.txt"; + public const string LocalVersion = "1.5.2"; + [MenuItem("ParrelSync/Check for update", priority = 20)] + static void CheckForUpdate() + { + using (System.Net.WebClient client = new System.Net.WebClient()) + { + try + { + //This won't work with UPM packages + //string localVersionText = AssetDatabase.LoadAssetAtPath(LocalVersionFilePath).text; + + string localVersionText = LocalVersion; + Debug.Log("Local version text : " + LocalVersion); + + string latesteVersionText = client.DownloadString(ExternalLinks.RemoteVersionURL); + Debug.Log("latest version text got: " + latesteVersionText); + string messageBody = "Current Version: " + localVersionText +"\n" + +"Latest Version: " + latesteVersionText + "\n"; + var latestVersion = new Version(latesteVersionText); + var localVersion = new Version(localVersionText); + + if (latestVersion > localVersion) + { + Debug.Log("There's a newer version"); + messageBody += "There's a newer version available"; + if(EditorUtility.DisplayDialog("Check for update.", messageBody, "Get latest release", "Close")) + { + Application.OpenURL(ExternalLinks.Releases); + } + } + else + { + Debug.Log("Current version is up-to-date."); + messageBody += "Current version is up-to-date."; + EditorUtility.DisplayDialog("Check for update.", messageBody,"OK"); + } + + } + catch (Exception exp) + { + Debug.LogError("Error with checking update. Exception: " + exp); + EditorUtility.DisplayDialog("Update Error","Error with checking update. \nSee console for more details.", + "OK" + ); + } + } + } + } +} diff --git a/Assets/ParrelSync/Editor/UpdateChecker.cs.meta b/Assets/ParrelSync/Editor/UpdateChecker.cs.meta new file mode 100644 index 0000000..8dcd733 --- /dev/null +++ b/Assets/ParrelSync/Editor/UpdateChecker.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d3453b3f1a20ea148b5028f8556a7be5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/Editor/ValidateCopiedFoldersIntegrity.cs b/Assets/ParrelSync/Editor/ValidateCopiedFoldersIntegrity.cs new file mode 100644 index 0000000..1ee73bc --- /dev/null +++ b/Assets/ParrelSync/Editor/ValidateCopiedFoldersIntegrity.cs @@ -0,0 +1,73 @@ +namespace ParrelSync +{ + using UnityEditor; + using UnityEngine; + using System; + using System.Text; + using System.Security.Cryptography; + using System.IO; + using System.Linq; + + [InitializeOnLoad] + public class ValidateCopiedFoldersIntegrity + { + const string SessionStateKey = "ValidateCopiedFoldersIntegrity_Init"; + /// + /// Called once on editor startup. + /// Validate copied folders integrity in clone project + /// + static ValidateCopiedFoldersIntegrity() + { + if (!SessionState.GetBool(SessionStateKey, false)) + { + SessionState.SetBool(SessionStateKey, true); + if (!ClonesManager.IsClone()) { return; } + + ValidateFolder(ClonesManager.GetCurrentProjectPath(), ClonesManager.GetOriginalProjectPath(), "Packages"); + } + } + + public static void ValidateFolder(string targetRoot, string originalRoot, string folderName) + { + var targetFolderPath = Path.Combine(targetRoot, folderName); + var targetFolderHash = CreateMd5ForFolder(targetFolderPath); + + var originalFolderPath = Path.Combine(originalRoot, folderName); + var originalFolderHash = CreateMd5ForFolder(originalFolderPath); + + if (targetFolderHash != originalFolderHash) + { + Debug.Log("ParrelSync: Detected changes in '" + folderName + "' directory. Updating cloned project..."); + FileUtil.ReplaceDirectory(originalFolderPath, targetFolderPath); + } + } + + static string CreateMd5ForFolder(string path) + { + // assuming you want to include nested folders + var files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories) + .OrderBy(p => p).ToList(); + + MD5 md5 = MD5.Create(); + + for (int i = 0; i < files.Count; i++) + { + string file = files[i]; + + // hash path + string relativePath = file.Substring(path.Length + 1); + byte[] pathBytes = Encoding.UTF8.GetBytes(relativePath.ToLower()); + md5.TransformBlock(pathBytes, 0, pathBytes.Length, pathBytes, 0); + + // hash contents + byte[] contentBytes = File.ReadAllBytes(file); + if (i == files.Count - 1) + md5.TransformFinalBlock(contentBytes, 0, contentBytes.Length); + else + md5.TransformBlock(contentBytes, 0, contentBytes.Length, contentBytes, 0); + } + + return BitConverter.ToString(md5.Hash).Replace("-", "").ToLower(); + } + } +} \ No newline at end of file diff --git a/Assets/ParrelSync/Editor/ValidateCopiedFoldersIntegrity.cs.meta b/Assets/ParrelSync/Editor/ValidateCopiedFoldersIntegrity.cs.meta new file mode 100644 index 0000000..daab522 --- /dev/null +++ b/Assets/ParrelSync/Editor/ValidateCopiedFoldersIntegrity.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d8fb344b9abf5274abd744833474b087 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/package.json b/Assets/ParrelSync/package.json new file mode 100644 index 0000000..08bb747 --- /dev/null +++ b/Assets/ParrelSync/package.json @@ -0,0 +1,10 @@ +{ + "name": "com.veriorpies.parrelsync", + "displayName": "ParrelSync", + "version": "1.5.2", + "unity": "2018.4", + "description": "ParrelSync is a Unity editor extension that allows users to test multiplayer gameplay without building the project by having another Unity editor window opened and mirror the changes from the original project.", + "license": "MIT", + "keywords": [ "Networking", "Utils", "Editor", "Extensions" ], + "dependencies": {} +} \ No newline at end of file diff --git a/Assets/ParrelSync/package.json.meta b/Assets/ParrelSync/package.json.meta new file mode 100644 index 0000000..4ced740 --- /dev/null +++ b/Assets/ParrelSync/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a2a889c264e34b47a7349cbcb2cbedd7 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/ParrelSync/projectCloner.asmdef b/Assets/ParrelSync/projectCloner.asmdef new file mode 100644 index 0000000..1e56b06 --- /dev/null +++ b/Assets/ParrelSync/projectCloner.asmdef @@ -0,0 +1,15 @@ +{ + "name": "ParrelSync", + "references": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/ParrelSync/projectCloner.asmdef.meta b/Assets/ParrelSync/projectCloner.asmdef.meta new file mode 100644 index 0000000..3aa8857 --- /dev/null +++ b/Assets/ParrelSync/projectCloner.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 894a6cc6ed5cd2645bb542978cbed6a9 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Resources/NetPlayer.prefab b/Assets/Resources/NetPlayer.prefab index 4930b28..b05afc9 100644 --- a/Assets/Resources/NetPlayer.prefab +++ b/Assets/Resources/NetPlayer.prefab @@ -117,6 +117,8 @@ GameObject: - component: {fileID: 418486312471410349} - component: {fileID: 7638642189276666675} - component: {fileID: -5533950480148800818} + - component: {fileID: 6776688211956608652} + - component: {fileID: 323403814507875080} m_Layer: 0 m_Name: NetPlayer m_TagString: Untagged @@ -202,3 +204,32 @@ MonoBehaviour: m_EditorClassIdentifier: _serverPosition: m_InternalValue: {x: 0, y: 0, z: 0} +--- !u!114 &6776688211956608652 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6690247107163074809} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8644473b781aeb140b723c0b2333be7b, type: 3} + m_Name: + m_EditorClassIdentifier: + inputVector: {x: 0, y: 0, z: 0} +--- !u!114 &323403814507875080 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6690247107163074809} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d1a31a292da0c5e44b4c426a6249c8c6, type: 3} + m_Name: + m_EditorClassIdentifier: + moveSpeed: 10 + position: {x: 0, y: 0, z: 0} + Networkposition: + m_InternalValue: {x: 0, y: 0, z: 0} diff --git a/Assets/Resources/PlayerInputActions.cs b/Assets/Resources/PlayerInputActions.cs new file mode 100644 index 0000000..0db5202 --- /dev/null +++ b/Assets/Resources/PlayerInputActions.cs @@ -0,0 +1,216 @@ +//------------------------------------------------------------------------------ +// +// This code was auto-generated by com.unity.inputsystem:InputActionCodeGenerator +// version 1.11.2 +// from Assets/Resources/PlayerInputActions.inputactions +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine.InputSystem; +using UnityEngine.InputSystem.Utilities; + +public partial class @PlayerInputActions: IInputActionCollection2, IDisposable +{ + public InputActionAsset asset { get; } + public @PlayerInputActions() + { + asset = InputActionAsset.FromJson(@"{ + ""name"": ""PlayerInputActions"", + ""maps"": [ + { + ""name"": ""PC"", + ""id"": ""31c4dc91-8321-4b69-8c84-0b29f099d8a4"", + ""actions"": [ + { + ""name"": ""moveControl"", + ""type"": ""Value"", + ""id"": ""cd962ed4-6b77-457a-a7bb-7c5c7cad9a5e"", + ""expectedControlType"": ""Vector2"", + ""processors"": """", + ""interactions"": """", + ""initialStateCheck"": true + } + ], + ""bindings"": [ + { + ""name"": ""2D Vector"", + ""id"": ""171c2437-b98a-4ebf-8020-920c9ee7a0d1"", + ""path"": ""2DVector"", + ""interactions"": """", + ""processors"": """", + ""groups"": """", + ""action"": ""moveControl"", + ""isComposite"": true, + ""isPartOfComposite"": false + }, + { + ""name"": ""up"", + ""id"": ""4c12b491-ddfa-475d-bf87-5dcbf530874c"", + ""path"": ""/w"", + ""interactions"": """", + ""processors"": """", + ""groups"": """", + ""action"": ""moveControl"", + ""isComposite"": false, + ""isPartOfComposite"": true + }, + { + ""name"": ""down"", + ""id"": ""f1f56ca7-5392-4ecc-8883-a9a974d0ddf7"", + ""path"": ""/s"", + ""interactions"": """", + ""processors"": """", + ""groups"": """", + ""action"": ""moveControl"", + ""isComposite"": false, + ""isPartOfComposite"": true + }, + { + ""name"": ""left"", + ""id"": ""dbc3810d-3bc6-40e7-990b-8de18cfa9bd2"", + ""path"": ""/a"", + ""interactions"": """", + ""processors"": """", + ""groups"": """", + ""action"": ""moveControl"", + ""isComposite"": false, + ""isPartOfComposite"": true + }, + { + ""name"": ""right"", + ""id"": ""01fbde0d-89c1-4567-8fc9-36befda08218"", + ""path"": ""/d"", + ""interactions"": """", + ""processors"": """", + ""groups"": """", + ""action"": ""moveControl"", + ""isComposite"": false, + ""isPartOfComposite"": true + } + ] + } + ], + ""controlSchemes"": [] +}"); + // PC + m_PC = asset.FindActionMap("PC", throwIfNotFound: true); + m_PC_moveControl = m_PC.FindAction("moveControl", throwIfNotFound: true); + } + + ~@PlayerInputActions() + { + UnityEngine.Debug.Assert(!m_PC.enabled, "This will cause a leak and performance issues, PlayerInputActions.PC.Disable() has not been called."); + } + + public void Dispose() + { + UnityEngine.Object.Destroy(asset); + } + + public InputBinding? bindingMask + { + get => asset.bindingMask; + set => asset.bindingMask = value; + } + + public ReadOnlyArray? devices + { + get => asset.devices; + set => asset.devices = value; + } + + public ReadOnlyArray controlSchemes => asset.controlSchemes; + + public bool Contains(InputAction action) + { + return asset.Contains(action); + } + + public IEnumerator GetEnumerator() + { + return asset.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Enable() + { + asset.Enable(); + } + + public void Disable() + { + asset.Disable(); + } + + public IEnumerable bindings => asset.bindings; + + public InputAction FindAction(string actionNameOrId, bool throwIfNotFound = false) + { + return asset.FindAction(actionNameOrId, throwIfNotFound); + } + + public int FindBinding(InputBinding bindingMask, out InputAction action) + { + return asset.FindBinding(bindingMask, out action); + } + + // PC + private readonly InputActionMap m_PC; + private List m_PCActionsCallbackInterfaces = new List(); + private readonly InputAction m_PC_moveControl; + public struct PCActions + { + private @PlayerInputActions m_Wrapper; + public PCActions(@PlayerInputActions wrapper) { m_Wrapper = wrapper; } + public InputAction @moveControl => m_Wrapper.m_PC_moveControl; + public InputActionMap Get() { return m_Wrapper.m_PC; } + public void Enable() { Get().Enable(); } + public void Disable() { Get().Disable(); } + public bool enabled => Get().enabled; + public static implicit operator InputActionMap(PCActions set) { return set.Get(); } + public void AddCallbacks(IPCActions instance) + { + if (instance == null || m_Wrapper.m_PCActionsCallbackInterfaces.Contains(instance)) return; + m_Wrapper.m_PCActionsCallbackInterfaces.Add(instance); + @moveControl.started += instance.OnMoveControl; + @moveControl.performed += instance.OnMoveControl; + @moveControl.canceled += instance.OnMoveControl; + } + + private void UnregisterCallbacks(IPCActions instance) + { + @moveControl.started -= instance.OnMoveControl; + @moveControl.performed -= instance.OnMoveControl; + @moveControl.canceled -= instance.OnMoveControl; + } + + public void RemoveCallbacks(IPCActions instance) + { + if (m_Wrapper.m_PCActionsCallbackInterfaces.Remove(instance)) + UnregisterCallbacks(instance); + } + + public void SetCallbacks(IPCActions instance) + { + foreach (var item in m_Wrapper.m_PCActionsCallbackInterfaces) + UnregisterCallbacks(item); + m_Wrapper.m_PCActionsCallbackInterfaces.Clear(); + AddCallbacks(instance); + } + } + public PCActions @PC => new PCActions(this); + public interface IPCActions + { + void OnMoveControl(InputAction.CallbackContext context); + } +} diff --git a/Assets/Resources/PlayerInputActions.cs.meta b/Assets/Resources/PlayerInputActions.cs.meta new file mode 100644 index 0000000..1f0bdc9 --- /dev/null +++ b/Assets/Resources/PlayerInputActions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d41ef2332cb8e5242a1c27781568cd34 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Resources/PlayerInputActions.inputactions b/Assets/Resources/PlayerInputActions.inputactions new file mode 100644 index 0000000..3116b53 --- /dev/null +++ b/Assets/Resources/PlayerInputActions.inputactions @@ -0,0 +1,78 @@ +{ + "name": "PlayerInputActions", + "maps": [ + { + "name": "PC", + "id": "31c4dc91-8321-4b69-8c84-0b29f099d8a4", + "actions": [ + { + "name": "moveControl", + "type": "Value", + "id": "cd962ed4-6b77-457a-a7bb-7c5c7cad9a5e", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true + } + ], + "bindings": [ + { + "name": "2D Vector", + "id": "171c2437-b98a-4ebf-8020-920c9ee7a0d1", + "path": "2DVector", + "interactions": "", + "processors": "", + "groups": "", + "action": "moveControl", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "4c12b491-ddfa-475d-bf87-5dcbf530874c", + "path": "/w", + "interactions": "", + "processors": "", + "groups": "", + "action": "moveControl", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "f1f56ca7-5392-4ecc-8883-a9a974d0ddf7", + "path": "/s", + "interactions": "", + "processors": "", + "groups": "", + "action": "moveControl", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "dbc3810d-3bc6-40e7-990b-8de18cfa9bd2", + "path": "/a", + "interactions": "", + "processors": "", + "groups": "", + "action": "moveControl", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "01fbde0d-89c1-4567-8fc9-36befda08218", + "path": "/d", + "interactions": "", + "processors": "", + "groups": "", + "action": "moveControl", + "isComposite": false, + "isPartOfComposite": true + } + ] + } + ], + "controlSchemes": [] +} \ No newline at end of file diff --git a/Assets/Resources/PlayerInputActions.inputactions.meta b/Assets/Resources/PlayerInputActions.inputactions.meta new file mode 100644 index 0000000..940fd89 --- /dev/null +++ b/Assets/Resources/PlayerInputActions.inputactions.meta @@ -0,0 +1,14 @@ +fileFormatVersion: 2 +guid: b11eb05e8af9e414497cbbc2a534300e +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3} + generateWrapperCode: 1 + wrapperCodePath: + wrapperClassName: + wrapperCodeNamespace: diff --git a/Assets/Scenes/SampleScene.unity b/Assets/Scenes/SampleScene.unity index 20ee3ba..e57b407 100644 --- a/Assets/Scenes/SampleScene.unity +++ b/Assets/Scenes/SampleScene.unity @@ -38,7 +38,6 @@ RenderSettings: m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 0} - m_IndirectSpecularColor: {r: 0.18028378, g: 0.22571412, b: 0.30692285, a: 1} m_UseRadianceAmbientProbe: 0 --- !u!157 &3 LightmapSettings: @@ -444,6 +443,72 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 4e2321277f6e4ea887086d0795f21336, type: 3} m_Name: m_EditorClassIdentifier: +--- !u!1 &720262207 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 720262210} + - component: {fileID: 720262208} + - component: {fileID: 720262211} + m_Layer: 0 + m_Name: PlayermoveSystem + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &720262208 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 720262207} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 2294826923 + InScenePlacedSourceGlobalObjectIdHash: 0 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!4 &720262210 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 720262207} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &720262211 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 720262207} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: ab9df984dc4ede348bb8882b650485ec, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1 &832575517 GameObject: m_ObjectHideFlags: 0 @@ -599,3 +664,4 @@ SceneRoots: - {fileID: 832575519} - {fileID: 574625151} - {fileID: 1142914855} + - {fileID: 720262210} diff --git a/Assets/Scripts/MoveMent.meta b/Assets/Scripts/MoveMent.meta new file mode 100644 index 0000000..b92e734 --- /dev/null +++ b/Assets/Scripts/MoveMent.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e235889db1c0467418ef28c28ed49637 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/MoveMent/Inputcompont.cs b/Assets/Scripts/MoveMent/Inputcompont.cs new file mode 100644 index 0000000..d49c15d --- /dev/null +++ b/Assets/Scripts/MoveMent/Inputcompont.cs @@ -0,0 +1,22 @@ +using UnityEngine; +using Unity.Netcode; + +public class Inputcompont : NetworkBehaviour +{ + public Vector3 inputVector; // 存储输入向量 + + private void Update() + { + // 如果是本地玩家,则获取输入 + if (IsOwner) + { + float h = Input.GetAxis("Horizontal"); + float v = Input.GetAxis("Vertical"); + inputVector = new Vector3(h,0,v); + } + else + { + inputVector = Vector3.zero; // 非本地玩家输入为零 + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/MoveMent/Inputcompont.cs.meta b/Assets/Scripts/MoveMent/Inputcompont.cs.meta new file mode 100644 index 0000000..74d00ee --- /dev/null +++ b/Assets/Scripts/MoveMent/Inputcompont.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8644473b781aeb140b723c0b2333be7b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/MoveMent/PlayermoveSystem.cs b/Assets/Scripts/MoveMent/PlayermoveSystem.cs new file mode 100644 index 0000000..5a10261 --- /dev/null +++ b/Assets/Scripts/MoveMent/PlayermoveSystem.cs @@ -0,0 +1,31 @@ +using UnityEngine; +using Unity.Netcode; + +public class PlayermoveSystem : NetworkBehaviour +{ + private void Update() + { + // 获取所有具有 InputRecive 和 MoveController 组件的玩家 + Inputcompont[] inputcomponts = FindObjectsOfType(); + Positioncompont[] positioncomponts = FindObjectsOfType(); + + // 遍历所有玩家 + for (int i = 0; i < inputcomponts.Length; i++) + { + Inputcompont inputcompont = inputcomponts[i]; + Positioncompont positioncompont = positioncomponts[i]; + + // 检查是否是本地玩家并且有输入 + //if (inputcompont.IsLocalPlayer && inputcompont.inputVector != Vector3.zero && positioncompont.IsOwner) + if (inputcompont.inputVector != Vector3.zero && positioncompont.IsOwner) + { + Debug.Log("IsOwner and inputVector is not zero"); + positioncompont.position += inputcompont.inputVector * positioncompont.moveSpeed * Time.deltaTime; + positioncompont.transform.position = positioncompont.position; + + // 同步位置到服务器和其他客户端 + positioncompont.Networkposition.Value = positioncompont.position; + } + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/MoveMent/PlayermoveSystem.cs.meta b/Assets/Scripts/MoveMent/PlayermoveSystem.cs.meta new file mode 100644 index 0000000..fe351c4 --- /dev/null +++ b/Assets/Scripts/MoveMent/PlayermoveSystem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ab9df984dc4ede348bb8882b650485ec +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Scripts/MoveMent/Positioncompont.cs b/Assets/Scripts/MoveMent/Positioncompont.cs new file mode 100644 index 0000000..c2a1058 --- /dev/null +++ b/Assets/Scripts/MoveMent/Positioncompont.cs @@ -0,0 +1,19 @@ +using UnityEngine; +using Unity.Netcode; + +public class Positioncompont : NetworkBehaviour +{ + public float moveSpeed = 10f; // 移动速度 + public Vector3 position; // 当前位置 + + public NetworkVariable Networkposition = new NetworkVariable(Vector3.zero); + + private void Update() + { + // 如果不是本地玩家,则从网络变量获取位置并应用 + if (!IsOwner) + { + transform.position = Networkposition.Value; + } + } +} \ No newline at end of file diff --git a/Assets/Scripts/MoveMent/Positioncompont.cs.meta b/Assets/Scripts/MoveMent/Positioncompont.cs.meta new file mode 100644 index 0000000..b11a80e --- /dev/null +++ b/Assets/Scripts/MoveMent/Positioncompont.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d1a31a292da0c5e44b4c426a6249c8c6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Settings/Locomotion.inputactions b/Assets/Settings/Locomotion.inputactions new file mode 100644 index 0000000..c422070 --- /dev/null +++ b/Assets/Settings/Locomotion.inputactions @@ -0,0 +1,838 @@ +{ + "name": "Locomotion", + "maps": [ + { + "name": "Player", + "id": "606bb473-450c-48af-ae65-649f8b545b6d", + "actions": [ + { + "name": "Move", + "type": "Value", + "id": "4b999e04-b8cc-40ea-b5c9-13107a2e8cbc", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "Look", + "type": "Value", + "id": "f4cdcd76-ba48-4a7e-b9a3-553b409ae144", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "Fire", + "type": "Button", + "id": "b6908a80-7849-4e06-a01f-5de780435717", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + } + ], + "bindings": [ + { + "name": "", + "id": "978bfe49-cc26-4a3d-ab7b-7d7a29327403", + "path": "/leftStick", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Move", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "WASD", + "id": "00ca640b-d935-4593-8157-c05846ea39b3", + "path": "Dpad", + "interactions": "", + "processors": "", + "groups": "", + "action": "Move", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "e2062cb9-1b15-46a2-838c-2f8d72a0bdd9", + "path": "/w", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "up", + "id": "8180e8bd-4097-4f4e-ab88-4523101a6ce9", + "path": "/upArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "320bffee-a40b-4347-ac70-c210eb8bc73a", + "path": "/s", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "1c5327b5-f71c-4f60-99c7-4e737386f1d1", + "path": "/downArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "d2581a9b-1d11-4566-b27d-b92aff5fabbc", + "path": "/a", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "2e46982e-44cc-431b-9f0b-c11910bf467a", + "path": "/leftArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "fcfe95b8-67b9-4526-84b5-5d0bc98d6400", + "path": "/d", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "77bff152-3580-4b21-b6de-dcd0c7e41164", + "path": "/rightArrow", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Move", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "", + "id": "1635d3fe-58b6-4ba9-a4e2-f4b964f6b5c8", + "path": "/{Primary2DAxis}", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Move", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "3ea4d645-4504-4529-b061-ab81934c3752", + "path": "/stick", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Move", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "c1f7a91b-d0fd-4a62-997e-7fb9b69bf235", + "path": "/rightStick", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Look", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8c8e490b-c610-4785-884f-f04217b23ca4", + "path": "/delta", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse;Touch", + "action": "Look", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "3e5f5442-8668-4b27-a940-df99bad7e831", + "path": "/{Hatswitch}", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Look", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "143bb1cd-cc10-4eca-a2f0-a3664166fe91", + "path": "/rightTrigger", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Fire", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "05f6913d-c316-48b2-a6bb-e225f14c7960", + "path": "/leftButton", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Fire", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "886e731e-7071-4ae4-95c0-e61739dad6fd", + "path": "/primaryTouch/tap", + "interactions": "", + "processors": "", + "groups": ";Touch", + "action": "Fire", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "ee3d0cd2-254e-47a7-a8cb-bc94d9658c54", + "path": "/trigger", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Fire", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8255d333-5683-4943-a58a-ccb207ff1dce", + "path": "/{PrimaryAction}", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Fire", + "isComposite": false, + "isPartOfComposite": false + } + ] + }, + { + "name": "UI", + "id": "a9c91929-a284-44fd-8932-fa7d997edf87", + "actions": [ + { + "name": "Navigate", + "type": "PassThrough", + "id": "3c48b137-7102-4c9f-8334-1cf8aa69d4e4", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Submit", + "type": "Button", + "id": "ec9fc9ea-84fb-4f98-bd08-d5fb3ade1893", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Cancel", + "type": "Button", + "id": "70f81e41-9ce8-4076-9fb6-57344933d55f", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "Point", + "type": "PassThrough", + "id": "6c7176ef-1e89-4a2c-ae83-8f5edf18a0e9", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "Click", + "type": "PassThrough", + "id": "d6224b8e-a0cd-4433-b876-a3289308ed15", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": true + }, + { + "name": "ScrollWheel", + "type": "PassThrough", + "id": "e5860ed3-981e-41ba-a2c6-0cffc101e9a6", + "expectedControlType": "Vector2", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "MiddleClick", + "type": "PassThrough", + "id": "a9a93b04-8197-4b84-ac64-fef097aeee19", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "RightClick", + "type": "PassThrough", + "id": "43a0c75f-35df-4e6d-8639-f14bf63e205d", + "expectedControlType": "Button", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "TrackedDevicePosition", + "type": "PassThrough", + "id": "63c70d8a-a85e-4468-8f70-6739506abc31", + "expectedControlType": "Vector3", + "processors": "", + "interactions": "", + "initialStateCheck": false + }, + { + "name": "TrackedDeviceOrientation", + "type": "PassThrough", + "id": "66de19e0-8c25-4da3-961a-63d219827fa8", + "expectedControlType": "Quaternion", + "processors": "", + "interactions": "", + "initialStateCheck": false + } + ], + "bindings": [ + { + "name": "Gamepad", + "id": "809f371f-c5e2-4e7a-83a1-d867598f40dd", + "path": "2DVector", + "interactions": "", + "processors": "", + "groups": "", + "action": "Navigate", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "14a5d6e8-4aaf-4119-a9ef-34b8c2c548bf", + "path": "/leftStick/up", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "up", + "id": "9144cbe6-05e1-4687-a6d7-24f99d23dd81", + "path": "/rightStick/up", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "2db08d65-c5fb-421b-983f-c71163608d67", + "path": "/leftStick/down", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "58748904-2ea9-4a80-8579-b500e6a76df8", + "path": "/rightStick/down", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "8ba04515-75aa-45de-966d-393d9bbd1c14", + "path": "/leftStick/left", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "712e721c-bdfb-4b23-a86c-a0d9fcfea921", + "path": "/rightStick/left", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "fcd248ae-a788-4676-a12e-f4d81205600b", + "path": "/leftStick/right", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "1f04d9bc-c50b-41a1-bfcc-afb75475ec20", + "path": "/rightStick/right", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "", + "id": "fb8277d4-c5cd-4663-9dc7-ee3f0b506d90", + "path": "/dpad", + "interactions": "", + "processors": "", + "groups": ";Gamepad", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "Joystick", + "id": "e25d9774-381c-4a61-b47c-7b6b299ad9f9", + "path": "2DVector", + "interactions": "", + "processors": "", + "groups": "", + "action": "Navigate", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "3db53b26-6601-41be-9887-63ac74e79d19", + "path": "/stick/up", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "0cb3e13e-3d90-4178-8ae6-d9c5501d653f", + "path": "/stick/down", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "0392d399-f6dd-4c82-8062-c1e9c0d34835", + "path": "/stick/left", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "942a66d9-d42f-43d6-8d70-ecb4ba5363bc", + "path": "/stick/right", + "interactions": "", + "processors": "", + "groups": "Joystick", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "Keyboard", + "id": "ff527021-f211-4c02-933e-5976594c46ed", + "path": "2DVector", + "interactions": "", + "processors": "", + "groups": "", + "action": "Navigate", + "isComposite": true, + "isPartOfComposite": false + }, + { + "name": "up", + "id": "563fbfdd-0f09-408d-aa75-8642c4f08ef0", + "path": "/w", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "up", + "id": "eb480147-c587-4a33-85ed-eb0ab9942c43", + "path": "/upArrow", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "2bf42165-60bc-42ca-8072-8c13ab40239b", + "path": "/s", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "down", + "id": "85d264ad-e0a0-4565-b7ff-1a37edde51ac", + "path": "/downArrow", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "74214943-c580-44e4-98eb-ad7eebe17902", + "path": "/a", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "left", + "id": "cea9b045-a000-445b-95b8-0c171af70a3b", + "path": "/leftArrow", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "8607c725-d935-4808-84b1-8354e29bab63", + "path": "/d", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "right", + "id": "4cda81dc-9edd-4e03-9d7c-a71a14345d0b", + "path": "/rightArrow", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Navigate", + "isComposite": false, + "isPartOfComposite": true + }, + { + "name": "", + "id": "9e92bb26-7e3b-4ec4-b06b-3c8f8e498ddc", + "path": "*/{Submit}", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse;Gamepad;Touch;Joystick;XR", + "action": "Submit", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "82627dcc-3b13-4ba9-841d-e4b746d6553e", + "path": "*/{Cancel}", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse;Gamepad;Touch;Joystick;XR", + "action": "Cancel", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "c52c8e0b-8179-41d3-b8a1-d149033bbe86", + "path": "/position", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Point", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "e1394cbc-336e-44ce-9ea8-6007ed6193f7", + "path": "/position", + "interactions": "", + "processors": "", + "groups": "Keyboard&Mouse", + "action": "Point", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "5693e57a-238a-46ed-b5ae-e64e6e574302", + "path": "/touch*/position", + "interactions": "", + "processors": "", + "groups": "Touch", + "action": "Point", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "4faf7dc9-b979-4210-aa8c-e808e1ef89f5", + "path": "/leftButton", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Click", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "8d66d5ba-88d7-48e6-b1cd-198bbfef7ace", + "path": "/tip", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "Click", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "47c2a644-3ebc-4dae-a106-589b7ca75b59", + "path": "/touch*/press", + "interactions": "", + "processors": "", + "groups": "Touch", + "action": "Click", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "bb9e6b34-44bf-4381-ac63-5aa15d19f677", + "path": "/trigger", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "Click", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "38c99815-14ea-4617-8627-164d27641299", + "path": "/scroll", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "ScrollWheel", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "24066f69-da47-44f3-a07e-0015fb02eb2e", + "path": "/middleButton", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "MiddleClick", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "4c191405-5738-4d4b-a523-c6a301dbf754", + "path": "/rightButton", + "interactions": "", + "processors": "", + "groups": ";Keyboard&Mouse", + "action": "RightClick", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "7236c0d9-6ca3-47cf-a6ee-a97f5b59ea77", + "path": "/devicePosition", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "TrackedDevicePosition", + "isComposite": false, + "isPartOfComposite": false + }, + { + "name": "", + "id": "23e01e3a-f935-4948-8d8b-9bcac77714fb", + "path": "/deviceRotation", + "interactions": "", + "processors": "", + "groups": "XR", + "action": "TrackedDeviceOrientation", + "isComposite": false, + "isPartOfComposite": false + } + ] + } + ], + "controlSchemes": [ + { + "name": "Keyboard&Mouse", + "bindingGroup": "Keyboard&Mouse", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + }, + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "Gamepad", + "bindingGroup": "Gamepad", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "Touch", + "bindingGroup": "Touch", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "Joystick", + "bindingGroup": "Joystick", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + }, + { + "name": "XR", + "bindingGroup": "XR", + "devices": [ + { + "devicePath": "", + "isOptional": false, + "isOR": false + } + ] + } + ] +} \ No newline at end of file diff --git a/Assets/Settings/Locomotion.inputactions.meta b/Assets/Settings/Locomotion.inputactions.meta new file mode 100644 index 0000000..074f0f2 --- /dev/null +++ b/Assets/Settings/Locomotion.inputactions.meta @@ -0,0 +1,14 @@ +fileFormatVersion: 2 +guid: 10d52279750c3ac4fa0617842e7ea0de +ScriptedImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 2 + userData: + assetBundleName: + assetBundleVariant: + script: {fileID: 11500000, guid: 8404be70184654265930450def6a9037, type: 3} + generateWrapperCode: 0 + wrapperCodePath: + wrapperClassName: + wrapperCodeNamespace: diff --git a/Assets/Settings/URP-HighFidelity-Renderer.asset b/Assets/Settings/URP-HighFidelity-Renderer.asset index d9cfc00..4f662f3 100644 --- a/Assets/Settings/URP-HighFidelity-Renderer.asset +++ b/Assets/Settings/URP-HighFidelity-Renderer.asset @@ -76,6 +76,12 @@ MonoBehaviour: type: 3} dataDrivenLensFlare: {fileID: 4800000, guid: 6cda457ac28612740adb23da5d39ea92, type: 3} + terrainDetailLitPS: {fileID: 4800000, guid: f6783ab646d374f94b199774402a5144, + type: 3} + terrainDetailGrassPS: {fileID: 4800000, guid: e507fdfead5ca47e8b9a768b51c291a1, + type: 3} + terrainDetailGrassBillboardPS: {fileID: 4800000, guid: 29868e73b638e48ca99a19ea58c48d90, + type: 3} m_AssetVersion: 2 m_OpaqueLayerMask: serializedVersion: 2 diff --git a/Packages/manifest.json b/Packages/manifest.json index 2719b08..30e62eb 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -4,6 +4,7 @@ "com.unity.ide.rider": "3.0.28", "com.unity.ide.visualstudio": "2.0.22", "com.unity.ide.vscode": "1.2.5", + "com.unity.inputsystem": "1.11.2", "com.unity.netcode.gameobjects": "1.8.1", "com.unity.render-pipelines.universal": "14.0.11", "com.unity.test-framework": "1.1.33", @@ -12,7 +13,6 @@ "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.9", "com.unity.ugui": "1.0.0", "com.unity.visualscripting": "1.9.2", - "com.veriorpies.parrelsync": "https://github.com/VeriorPies/ParrelSync.git?path=/ParrelSync", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", "com.unity.modules.animation": "1.0.0", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 4076ad6..f42282a 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -1,21 +1,21 @@ { "dependencies": { "com.unity.burst": { - "version": "1.8.13", + "version": "1.8.19", "depth": 1, "source": "registry", "dependencies": { "com.unity.mathematics": "1.2.1", "com.unity.modules.jsonserialize": "1.0.0" }, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.collab-proxy": { "version": "2.3.1", "depth": 0, "source": "registry", "dependencies": {}, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.collections": { "version": "1.2.4", @@ -25,14 +25,14 @@ "com.unity.burst": "1.6.6", "com.unity.test-framework": "1.1.31" }, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.ext.nunit": { "version": "1.0.6", "depth": 1, "source": "registry", "dependencies": {}, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.ide.rider": { "version": "3.0.28", @@ -41,7 +41,7 @@ "dependencies": { "com.unity.ext.nunit": "1.0.6" }, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.ide.visualstudio": { "version": "2.0.22", @@ -50,41 +50,50 @@ "dependencies": { "com.unity.test-framework": "1.1.9" }, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.ide.vscode": { "version": "1.2.5", "depth": 0, "source": "registry", "dependencies": {}, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" + }, + "com.unity.inputsystem": { + "version": "1.11.2", + "depth": 0, + "source": "registry", + "dependencies": { + "com.unity.modules.uielements": "1.0.0" + }, + "url": "https://packages.unity.com" }, "com.unity.mathematics": { "version": "1.2.6", "depth": 1, "source": "registry", "dependencies": {}, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.netcode.gameobjects": { "version": "1.8.1", "depth": 0, "source": "registry", "dependencies": { - "com.unity.nuget.mono-cecil": "1.10.1", - "com.unity.transport": "1.4.0" + "com.unity.transport": "1.4.0", + "com.unity.nuget.mono-cecil": "1.10.1" }, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.nuget.mono-cecil": { "version": "1.11.4", "depth": 1, "source": "registry", "dependencies": {}, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.render-pipelines.core": { - "version": "14.0.11", + "version": "14.0.12", "depth": 1, "source": "builtin", "dependencies": { @@ -101,8 +110,8 @@ "dependencies": { "com.unity.mathematics": "1.2.1", "com.unity.burst": "1.8.9", - "com.unity.render-pipelines.core": "14.0.11", - "com.unity.shadergraph": "14.0.11", + "com.unity.render-pipelines.core": "14.0.12", + "com.unity.shadergraph": "14.0.12", "com.unity.render-pipelines.universal-config": "14.0.9" } }, @@ -119,14 +128,14 @@ "depth": 2, "source": "registry", "dependencies": {}, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.shadergraph": { - "version": "14.0.11", + "version": "14.0.12", "depth": 1, "source": "builtin", "dependencies": { - "com.unity.render-pipelines.core": "14.0.11", + "com.unity.render-pipelines.core": "14.0.12", "com.unity.searcher": "4.9.2" } }, @@ -135,7 +144,7 @@ "depth": 1, "source": "registry", "dependencies": {}, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.sysroot.linux-x86_64": { "version": "2.0.9", @@ -144,7 +153,7 @@ "dependencies": { "com.unity.sysroot": "2.0.10" }, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.test-framework": { "version": "1.1.33", @@ -155,7 +164,7 @@ "com.unity.modules.imgui": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0" }, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.textmeshpro": { "version": "3.0.6", @@ -164,19 +173,19 @@ "dependencies": { "com.unity.ugui": "1.0.0" }, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.timeline": { "version": "1.7.6", "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.audio": "1.0.0", "com.unity.modules.particlesystem": "1.0.0" }, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.toolchain.win-x86_64-linux-x86_64": { "version": "2.0.9", @@ -186,18 +195,18 @@ "com.unity.sysroot": "2.0.10", "com.unity.sysroot.linux-x86_64": "2.0.9" }, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.transport": { - "version": "1.4.1", + "version": "1.5.0", "depth": 1, "source": "registry", "dependencies": { - "com.unity.collections": "1.2.4", "com.unity.burst": "1.6.6", + "com.unity.collections": "1.2.4", "com.unity.mathematics": "1.2.6" }, - "url": "https://packages.unity.cn" + "url": "https://packages.unity.com" }, "com.unity.ugui": { "version": "1.0.0", @@ -216,14 +225,7 @@ "com.unity.ugui": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0" }, - "url": "https://packages.unity.cn" - }, - "com.veriorpies.parrelsync": { - "version": "https://github.com/VeriorPies/ParrelSync.git?path=/ParrelSync", - "depth": 0, - "source": "git", - "dependencies": {}, - "hash": "d1ef610e9fe3ad0b490cce91594449fc0af809a6" + "url": "https://packages.unity.com" }, "com.unity.modules.ai": { "version": "1.0.0", diff --git a/ProjectSettings/PackageManagerSettings.asset b/ProjectSettings/PackageManagerSettings.asset index 5d22f8c..f8ea226 100644 --- a/ProjectSettings/PackageManagerSettings.asset +++ b/ProjectSettings/PackageManagerSettings.asset @@ -21,7 +21,7 @@ MonoBehaviour: m_Registries: - m_Id: main m_Name: - m_Url: https://packages.unity.cn + m_Url: https://packages.unity.com m_Scopes: [] m_IsDefault: 1 m_Capabilities: 7 @@ -31,6 +31,6 @@ MonoBehaviour: m_RegistryInfoDraft: m_Modified: 0 m_ErrorMessage: - m_UserModificationsInstanceId: -854 - m_OriginalInstanceId: -856 + m_UserModificationsInstanceId: -860 + m_OriginalInstanceId: -862 m_LoadAssets: 0 diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt index e621526..75f9809 100644 --- a/ProjectSettings/ProjectVersion.txt +++ b/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2022.3.25f1c1 -m_EditorVersionWithRevision: 2022.3.25f1c1 (b29a64170038) +m_EditorVersion: 2022.3.60f1 +m_EditorVersionWithRevision: 2022.3.60f1 (5f63fdee6d95) diff --git a/vcc.userproperties b/vcc.userproperties new file mode 100644 index 0000000..e69de29