11 Commits

Author SHA1 Message Date
MrUnknownDE 49b1b6cd30 add discord rpc client 2026-04-06 19:52:29 +02:00
MrUnknownDE ed40046c48 add init setup with remote-url 2026-04-06 19:52:21 +02:00
MrUnknownDE 236a7562ec Merge branch 'gh-pages' of github.com:MrUnknownDE/unity-gittool into gh-pages 2026-04-06 18:47:56 +02:00
MrUnknownDE 341073ef91 remove some automatic 2026-04-06 18:47:53 +02:00
MrUnknownDE c7d6aa4754 Delete CNAME 2026-04-06 18:47:14 +02:00
MrUnknownDE 2cf94acaa5 manuell version 2026-04-06 18:46:25 +02:00
github-actions[bot] f1a9e81de9 Auto-update VCC index.json for v1.0.4 2026-04-06 16:40:20 +00:00
MrUnknownDE 54d62ae8fa weee 2026-04-06 18:32:57 +02:00
MrUnknownDE 2cb115aa8a add some version 2026-04-06 18:30:18 +02:00
github-actions[bot] e070c2c4b4 Auto-update VCC index.json for v1.0.3 2026-04-06 16:19:43 +00:00
github-actions[bot] 6481ee1a19 Add version v1.0.2 to VCC index 2026-04-06 16:12:02 +00:00
7 changed files with 225 additions and 927 deletions
-64
View File
@@ -1,64 +0,0 @@
using UdonSharp;
using UnityEngine;
using AudioLink;
public class AudioLinkBeatDetector : UdonSharpBehaviour
{
public AudioLink.AudioLink audioLinkInstance;
[Header("Settings")]
[Range(0, 1)] public float threshold = 0.5f;
public float minBpm = 70f;
public float maxBpm = 210f; // Alles darüber wird als Fehler ignoriert
[Header("Output")]
public float bpm = 128f;
public float instantBpm = 128f;
public bool isBeat;
private float lastBeatTime;
void Update()
{
if (audioLinkInstance == null || !audioLinkInstance.AudioDataIsAvailable()) return;
Vector2 bassPos = AudioLink.AudioLink.GetALPassAudioBass();
Vector4 data = audioLinkInstance.GetDataAtPixel((int)bassPos.x, (int)bassPos.y);
float currentLevel = data.x;
// Prüfen auf Threshold
if (currentLevel > threshold)
{
float currentTime = Time.time;
float timeDiff = currentTime - lastBeatTime;
// Der "Debounce" Check:
// 0.27s entspricht ca. 222 BPM. Alles was schneller kommt, ist Rauschen.
if (timeDiff > 0.27f)
{
if (lastBeatTime > 0)
{
float detected = 60f / timeDiff;
// Nur plausible Werte übernehmen
if (detected >= minBpm && detected <= maxBpm)
{
instantBpm = detected;
// Glättung: 0.5 sorgt für schnelles Folgen, aber filtert Ausreißer
bpm = Mathf.Lerp(bpm, instantBpm, 0.5f);
}
}
lastBeatTime = currentTime;
isBeat = true;
}
else
{
isBeat = false;
}
}
else
{
isBeat = false;
}
}
}
-43
View File
@@ -1,43 +0,0 @@
using UdonSharp;
using UnityEngine;
using UnityEngine.UI;
using TMPro;
public class BeatDetectorDisplay : UdonSharpBehaviour
{
public AudioLinkBeatDetector engine;
public TextMeshProUGUI bpmText;
public Image beatIndicator;
public Color activeColor = Color.cyan;
private float flashTimer;
void Update()
{
if (engine == null) return;
// Wenn die Engine einen Beat erkennt...
if (engine.isBeat)
{
// ...aktualisieren wir SOFORT den Text mit der instantBpm
if (bpmText != null)
{
bpmText.text = engine.instantBpm.ToString("F0"); // "F0" für ganze Zahlen ohne Lag-Gefühl
}
// Visueller Kick
flashTimer = 0.1f;
if (beatIndicator != null) beatIndicator.color = activeColor;
}
// Timer für das Abklingen der LED
if (flashTimer > 0)
{
flashTimer -= Time.deltaTime;
if (flashTimer <= 0 && beatIndicator != null)
{
beatIndicator.color = new Color(0.1f, 0.1f, 0.1f, 1f);
}
}
}
}
+175 -413
View File
@@ -7,68 +7,39 @@ using System.Collections.Generic;
public class GitPanel : EditorWindow public class GitPanel : EditorWindow
{ {
private string commitMessage = ""; private string commitMessage = "";
private string remoteUrlInput = ""; private string remoteUrlInput = ""; // NEU: Speicher für die Remote-URL
private string newBranchName = "";
private bool isGitInstalled = true;
private bool hasRepo = false; private bool hasRepo = false;
private bool settingsCorrect = true;
private string settingsWarning = "";
private string currentBranchName = "unknown";
private string[] availableBranches = new string[0];
private int selectedBranchIndex = 0;
private string[] changedFiles = new string[0]; private string[] changedFiles = new string[0];
private Vector2 scrollPositionChanges; private Vector2 scrollPositionChanges;
private Vector2 scrollPositionHistory; private Vector2 scrollPositionHistory;
// STATISCH: Damit RunGitCommand darauf zugreifen kann und das Log beim Neuladen erhalten bleibt!
private static string gitLogOutput = "";
private Vector2 scrollPositionLog;
private int selectedTab = 0; private int selectedTab = 0;
private string[] tabNames = { "Changes", "History" }; private string[] tabNames = { "Changes", "History" };
private bool showSettings = false;
private string webUrlOverride = "";
private string prefsKey = "";
private struct CommitInfo { public string hash; public string date; public string message; } private struct CommitInfo { public string hash; public string date; public string message; }
private List<CommitInfo> commitHistory = new List<CommitInfo>(); private List<CommitInfo> commitHistory = new List<CommitInfo>();
[MenuItem("Tools/MrUnknownDE/GIT Version Control")] [MenuItem("Tools/MrUnknownDE/Git-Tool")]
public static void ShowWindow() public static void ShowWindow()
{ {
GitPanel window = GetWindow<GitPanel>("GIT Version Control System"); GitPanel window = GetWindow<GitPanel>("Source Control");
window.minSize = new Vector2(380, 650); window.minSize = new Vector2(350, 500);
} }
private void OnEnable() private void OnEnable()
{ {
prefsKey = $"GitTool_WebUrl_{Application.dataPath.GetHashCode()}";
webUrlOverride = EditorPrefs.GetString(prefsKey, "");
RefreshData(); RefreshData();
} }
private void OnFocus() { RefreshData(); } private void OnFocus()
{
RefreshData();
}
public void RefreshData() public void RefreshData()
{ {
CheckGitInstallation();
CheckUnitySettings();
if (!isGitInstalled) return;
CheckRepoStatus(); CheckRepoStatus();
if (hasRepo)
{
ExportPackageInventory();
currentBranchName = RunGitCommand("rev-parse --abbrev-ref HEAD").Trim();
FetchBranches();
}
if (string.IsNullOrWhiteSpace(commitMessage) || commitMessage.StartsWith("Auto-Save:")) if (string.IsNullOrWhiteSpace(commitMessage) || commitMessage.StartsWith("Auto-Save:"))
{ {
SetDefaultCommitMessage(); SetDefaultCommitMessage();
@@ -77,87 +48,21 @@ public class GitPanel : EditorWindow
Repaint(); Repaint();
} }
private void CheckGitInstallation() private void SetDefaultCommitMessage()
{ {
try { commitMessage = $"Auto-Save: {System.DateTime.Now:yyyy-MM-dd HH:mm:ss}";
ProcessStartInfo startInfo = new ProcessStartInfo("git", "--version") { UseShellExecute = false, RedirectStandardOutput = true, CreateNoWindow = true };
using (Process p = Process.Start(startInfo)) { p.WaitForExit(); isGitInstalled = true; }
} catch { isGitInstalled = false; }
} }
// FIX: Nutzt jetzt die aktuelle Unity-API (VersionControlSettings.mode)
private void CheckUnitySettings()
{
settingsCorrect = true;
settingsWarning = "";
if (VersionControlSettings.mode != "Visible Meta Files")
{
settingsCorrect = false;
settingsWarning += "• Version Control Mode must be 'Visible Meta Files'\n";
}
if (EditorSettings.serializationMode != SerializationMode.ForceText)
{
settingsCorrect = false;
settingsWarning += "• Asset Serialization must be 'Force Text'\n";
}
}
private void FixUnitySettings()
{
VersionControlSettings.mode = "Visible Meta Files";
EditorSettings.serializationMode = SerializationMode.ForceText;
UnityEngine.Debug.Log("Git-Tool: Unity Project Settings updated for Git compatibility.");
RefreshData();
}
private void SetDefaultCommitMessage() { commitMessage = $"Auto-Save: {System.DateTime.Now:yyyy-MM-dd HH:mm:ss}"; }
private void OnGUI() private void OnGUI()
{ {
GUILayout.Space(10); GUILayout.Space(10);
GUILayout.Label("GIT Version Control System", EditorStyles.boldLabel); GUILayout.Label("SOURCE CONTROL", EditorStyles.boldLabel);
if (hasRepo) GUILayout.Label($"Active Branch: {currentBranchName}", EditorStyles.miniLabel);
GUILayout.Space(5); GUILayout.Space(5);
if (!settingsCorrect) if (!hasRepo)
{ {
EditorGUILayout.BeginVertical("box"); RenderInitUI();
EditorGUILayout.HelpBox("INCOMPATIBLE PROJECT SETTINGS:\n" + settingsWarning, MessageType.Error); return;
GUI.backgroundColor = new Color(1f, 0.5f, 0f);
if (GUILayout.Button("Fix Project Settings Now"))
{
FixUnitySettings();
}
GUI.backgroundColor = Color.white;
EditorGUILayout.EndVertical();
GUILayout.Space(10);
}
if (!isGitInstalled) { RenderGitMissingUI(); return; }
if (!hasRepo) { RenderInitUI(); return; }
showSettings = EditorGUILayout.Foldout(showSettings, "⚙️ Repository Settings");
if (showSettings)
{
EditorGUILayout.BeginVertical("box");
GUILayout.Label("Web Override (For custom SSH instances)", EditorStyles.miniBoldLabel);
EditorGUI.BeginChangeCheck();
webUrlOverride = EditorGUILayout.TextField("Web URL:", webUrlOverride);
if (EditorGUI.EndChangeCheck())
{
EditorPrefs.SetString(prefsKey, webUrlOverride.Trim());
}
GUILayout.Space(10);
GUILayout.Label("Inventory Management", EditorStyles.miniBoldLabel);
if (GUILayout.Button("📄 Sync Package Inventory (Unity & VRChat)", GUILayout.Height(25)))
{
ExportPackageInventory();
}
EditorGUILayout.EndVertical();
GUILayout.Space(5);
} }
selectedTab = GUILayout.Toolbar(selectedTab, tabNames, GUILayout.Height(25)); selectedTab = GUILayout.Toolbar(selectedTab, tabNames, GUILayout.Height(25));
@@ -167,411 +72,268 @@ public class GitPanel : EditorWindow
else RenderHistoryUI(); else RenderHistoryUI();
} }
private void RenderGitMissingUI()
{
EditorGUILayout.HelpBox("CRITICAL: Git not found.", MessageType.Error);
if (GUILayout.Button("Download Git for Windows", GUILayout.Height(30))) Application.OpenURL("https://git-scm.com/download/win");
}
private void RenderInitUI() private void RenderInitUI()
{ {
EditorGUILayout.HelpBox("No local Git repository found.", MessageType.Warning); EditorGUILayout.HelpBox("No local Git repository found. Initialize current project folder?", MessageType.Warning);
remoteUrlInput = EditorGUILayout.TextField("Remote URL:", remoteUrlInput);
GUILayout.Space(10);
GUILayout.Label("Remote Repository URL (Optional):", EditorStyles.boldLabel);
remoteUrlInput = EditorGUILayout.TextField(remoteUrlInput, GUILayout.Height(25));
EditorGUILayout.HelpBox("e.g., https://github.com/user/repo.git or git@gitea.domain.com:user/repo.git", MessageType.Info);
GUILayout.Space(10);
if (GUILayout.Button("Initialize Repository", GUILayout.Height(30))) if (GUILayout.Button("Initialize Repository", GUILayout.Height(30)))
{ {
RunGitCommand("init", true); RunGitCommand("init");
RunGitCommand("branch -M main", true);
if (!string.IsNullOrWhiteSpace(remoteUrlInput)) { // Branch direkt sauber auf "main" setzen (verhindert Konflikte mit alten "master" Defaults)
RunGitCommand($"remote add origin \"{remoteUrlInput.Trim()}\"", true); RunGitCommand("branch -M main");
RunGitCommand("pull origin main --allow-unrelated-histories --no-edit", true);
// Remote URL hinzufügen, falls angegeben
if (!string.IsNullOrWhiteSpace(remoteUrlInput))
{
RunGitCommand($"remote add origin \"{remoteUrlInput.Trim()}\"");
} }
GenerateUnityGitIgnore(); GenerateUnityGitIgnore();
AssetDatabase.Refresh(); RunGitCommand("add .gitignore");
RunGitCommand("add .gitignore", true); RunGitCommand("commit -m \"Initial commit (GitIgnore)\"");
RunGitCommand("commit -m \"Initial commit (GitIgnore)\"", true);
if (!string.IsNullOrWhiteSpace(remoteUrlInput)) RunGitCommand("push -u origin main", true); // Initialen Push ausführen, wenn ein Remote existiert
if (!string.IsNullOrWhiteSpace(remoteUrlInput))
{
RunGitCommand("push -u origin main");
UnityEngine.Debug.Log("Git-Tool: Repository initialized and pushed to remote!");
}
else
{
UnityEngine.Debug.Log("Git-Tool: Local repository initialized successfully.");
}
RefreshData(); RefreshData();
} }
} }
private void RenderGitUI() private void RenderGitUI()
{ {
EditorGUILayout.BeginVertical("box");
GUILayout.Label("Branch Management", EditorStyles.boldLabel);
if (availableBranches.Length > 0)
{
EditorGUI.BeginChangeCheck();
int newIndex = EditorGUILayout.Popup("Switch Branch:", selectedBranchIndex, availableBranches);
if (EditorGUI.EndChangeCheck() && newIndex != selectedBranchIndex)
{
RunGitCommand($"checkout \"{availableBranches[newIndex]}\"", true);
AssetDatabase.Refresh();
RefreshData();
return;
}
}
EditorGUILayout.BeginHorizontal();
newBranchName = EditorGUILayout.TextField("New Branch:", newBranchName);
GUI.backgroundColor = new Color(0.2f, 0.6f, 0.2f);
if (GUILayout.Button("+ Create", GUILayout.Width(80)))
{
if (!string.IsNullOrWhiteSpace(newBranchName))
{
RunGitCommand($"checkout -b \"{newBranchName.Trim()}\"", true);
newBranchName = "";
RefreshData();
GUI.FocusControl(null);
return;
}
}
GUI.backgroundColor = Color.white;
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndVertical();
GUILayout.Space(10);
commitMessage = EditorGUILayout.TextField(commitMessage, GUILayout.Height(25)); commitMessage = EditorGUILayout.TextField(commitMessage, GUILayout.Height(25));
EditorGUILayout.BeginHorizontal();
GUI.backgroundColor = new Color(0.2f, 0.4f, 0.8f); GUI.backgroundColor = new Color(0.2f, 0.4f, 0.8f);
if (GUILayout.Button("✓ Push", GUILayout.Height(30))) if (GUILayout.Button("✓ Commit & Push", GUILayout.Height(30)))
{ {
UnityEngine.Debug.Log("Git-Tool: Saving Scenes and Assets before push..."); if (string.IsNullOrWhiteSpace(commitMessage)) commitMessage = $"Auto-Save: {System.DateTime.Now:yyyy-MM-dd HH:mm:ss}";
UnityEditor.SceneManagement.EditorSceneManager.SaveOpenScenes();
AssetDatabase.SaveAssets();
if (string.IsNullOrWhiteSpace(commitMessage)) SetDefaultCommitMessage(); RunGitCommand("add .");
RunGitCommand("add .", true); RunGitCommand($"commit -m \"{commitMessage}\"");
RunGitCommand($"commit -m \"{commitMessage}\"", true); RunGitCommand("push");
string pushResult = RunGitCommand("push -u origin HEAD", true);
if (pushResult.Contains("rejected") || pushResult.Contains("fetch first"))
{
UnityEngine.Debug.LogError("Git-Tool: PUSH REJECTED! Jemand anderes hat Änderungen hochgeladen. Bitte klicke zuerst auf 'Pull'.");
}
else
{
UnityEngine.Debug.Log("Git-Tool: Changes successfully pushed!");
commitMessage = ""; commitMessage = "";
}
LiveSyncPanel.BroadcastGitUpdate();
RefreshData(); RefreshData();
} UnityEngine.Debug.Log("Git-Tool: Changes successfully pushed!");
GUI.backgroundColor = new Color(0.8f, 0.6f, 0.2f);
if (GUILayout.Button("⬇️ Pull", GUILayout.Width(80), GUILayout.Height(30)))
{
UnityEditor.SceneManagement.EditorSceneManager.SaveOpenScenes();
AssetDatabase.SaveAssets();
string pullResult = RunGitCommand("pull", true);
AssetDatabase.Refresh();
if (pullResult.Contains("CONFLICT"))
{
UnityEngine.Debug.LogError("Git-Tool: MERGE CONFLICT! Bitte in VS Code auflösen!");
EditorUtility.DisplayDialog("Merge Conflict", "Es gibt Konflikte mit den Server-Daten!\n\nGit konnte die Änderungen nicht automatisch zusammenführen. Bitte öffne die roten Dateien in deinem Code-Editor und löse den Konflikt manuell auf.", "OK");
}
RefreshData();
}
GUI.backgroundColor = new Color(0.8f, 0.3f, 0.3f);
if (GUILayout.Button("⎌ Revert", GUILayout.Width(80), GUILayout.Height(30)))
{
if (EditorUtility.DisplayDialog("Revert Changes?", "Discard ALL uncommitted changes?", "Yes", "Cancel")) {
RunGitCommand("reset --hard HEAD", true);
RunGitCommand("clean -fd", true);
AssetDatabase.Refresh();
RefreshData();
}
} }
GUI.backgroundColor = Color.white; GUI.backgroundColor = Color.white;
EditorGUILayout.EndHorizontal();
GUILayout.Space(10); GUILayout.Space(10);
EditorGUILayout.HelpBox("Legend: [M] Modified | [A] Added | [D] Deleted | [??] Untracked", MessageType.Info);
GUILayout.Space(5);
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
GUILayout.Label($"CHANGES ({changedFiles.Length})", EditorStyles.boldLabel); GUILayout.Label($"CHANGES ({changedFiles.Length})", EditorStyles.boldLabel);
if (GUILayout.Button("↻", GUILayout.Width(25))) RefreshData(); if (GUILayout.Button("↻", GUILayout.Width(25)))
{
RefreshData();
}
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
scrollPositionChanges = EditorGUILayout.BeginScrollView(scrollPositionChanges, "box"); scrollPositionChanges = EditorGUILayout.BeginScrollView(scrollPositionChanges, "box");
if (changedFiles.Length == 0) GUILayout.Label("No changes.");
if (changedFiles.Length == 0) GUILayout.Label("No unsaved changes.");
else RenderFileList(changedFiles); else RenderFileList(changedFiles);
EditorGUILayout.EndScrollView();
GUILayout.Space(5);
EditorGUILayout.BeginHorizontal();
GUILayout.Label("GIT CONSOLE", EditorStyles.boldLabel);
if (GUILayout.Button("Clear", GUILayout.Width(50))) gitLogOutput = "";
EditorGUILayout.EndHorizontal();
scrollPositionLog = EditorGUILayout.BeginScrollView(scrollPositionLog, "box", GUILayout.Height(120));
GUIStyle logStyle = new GUIStyle(EditorStyles.label) { wordWrap = true, fontSize = 10 };
GUILayout.Label(string.IsNullOrEmpty(gitLogOutput) ? "Ready." : gitLogOutput, logStyle);
EditorGUILayout.EndScrollView(); EditorGUILayout.EndScrollView();
} }
private void RenderHistoryUI() private void RenderHistoryUI()
{ {
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
GUILayout.Label("LAST COMMITS", EditorStyles.boldLabel); GUILayout.Label("LAST COMMITS (Click to open in Browser)", EditorStyles.boldLabel);
if (GUILayout.Button("↻", GUILayout.Width(25))) FetchHistory(); if (GUILayout.Button("↻", GUILayout.Width(25))) FetchHistory();
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
GUILayout.Space(5);
if (commitHistory.Count == 0)
{
GUILayout.Label("No commits found.");
return;
}
scrollPositionHistory = EditorGUILayout.BeginScrollView(scrollPositionHistory, "box"); scrollPositionHistory = EditorGUILayout.BeginScrollView(scrollPositionHistory, "box");
foreach (var commit in commitHistory) {
foreach (var commit in commitHistory)
{
Rect rect = EditorGUILayout.GetControlRect(false, 22); Rect rect = EditorGUILayout.GetControlRect(false, 22);
if (rect.Contains(Event.current.mousePosition)) EditorGUI.DrawRect(rect, new Color(1f, 1f, 1f, 0.1f));
GUI.Label(rect, $"<b>{commit.hash}</b> | {commit.date} | {commit.message}", new GUIStyle(EditorStyles.label){richText=true}); if (rect.Contains(Event.current.mousePosition))
if (Event.current.type == EventType.MouseDown && rect.Contains(Event.current.mousePosition)) { {
EditorGUI.DrawRect(rect, new Color(1f, 1f, 1f, 0.1f));
}
GUIStyle textStyle = new GUIStyle(EditorStyles.label) { richText = true };
GUI.Label(rect, $"<b>{commit.hash}</b> | {commit.date} | {commit.message}", textStyle);
Event e = Event.current;
if (e.type == EventType.MouseDown && e.button == 0 && rect.Contains(e.mousePosition))
{
OpenCommitInBrowser(commit.hash); OpenCommitInBrowser(commit.hash);
Event.current.Use(); e.Use();
} }
GUILayout.Space(2);
} }
EditorGUILayout.EndScrollView(); EditorGUILayout.EndScrollView();
} }
private void RenderFileList(string[] files) private void RenderFileList(string[] files)
{ {
foreach (string line in files) { foreach (string line in files)
{
if (line.Length < 4) continue; if (line.Length < 4) continue;
string status = line.Substring(0, 2).Trim();
string path = line.Substring(3).Trim().Replace("\"", ""); string status = line.Substring(0, 1).Trim() == "" ? line.Substring(0, 2) : line.Substring(0, 1);
string path = line.Substring(line.IndexOf('\t') + 1 > 0 ? line.IndexOf('\t') + 1 : 3).Trim();
if (path.StartsWith("\"") && path.EndsWith("\"")) path = path.Substring(1, path.Length - 2);
Rect rect = EditorGUILayout.GetControlRect(false, 18); Rect rect = EditorGUILayout.GetControlRect(false, 18);
if (rect.Contains(Event.current.mousePosition)) EditorGUI.DrawRect(rect, new Color(1f, 1f, 1f, 0.1f)); if (rect.Contains(Event.current.mousePosition)) EditorGUI.DrawRect(rect, new Color(1f, 1f, 1f, 0.1f));
GUI.Label(rect, $"[{status}] {path}");
if (Event.current.type == EventType.MouseDown && rect.Contains(Event.current.mousePosition)) { GUIStyle labelStyle = new GUIStyle(EditorStyles.label);
if (Event.current.clickCount == 1) PingAsset(path); GUI.Label(rect, $"[{status.Trim()}] {path}", labelStyle);
else if (Event.current.clickCount == 2) GitDiffViewer.ShowWindow(path, status);
Event.current.Use(); Event e = Event.current;
if (e.isMouse && e.button == 0 && rect.Contains(e.mousePosition) && e.type == EventType.MouseDown)
{
if (e.clickCount == 1) PingAsset(path);
else if (e.clickCount == 2) GitDiffViewer.ShowWindow(path, status);
e.Use();
} }
} }
} }
private void OpenCommitInBrowser(string hash) private void OpenCommitInBrowser(string hash)
{ {
if (!string.IsNullOrWhiteSpace(webUrlOverride)) string remoteUrl = RunGitCommand("config --get remote.origin.url").Trim();
if (string.IsNullOrEmpty(remoteUrl))
{ {
string url = webUrlOverride; UnityEngine.Debug.LogWarning("Git-Tool: No remote repository configured (origin missing).");
if (url.EndsWith("/")) url = url.Substring(0, url.Length - 1);
if (url.EndsWith(".git")) url = url.Substring(0, url.Length - 4);
Application.OpenURL($"{url}/commit/{hash}");
return; return;
} }
string remoteUrl = RunGitCommand("config --get remote.origin.url").Trim(); string webUrl = remoteUrl;
if (string.IsNullOrEmpty(remoteUrl)) return;
if (remoteUrl.StartsWith("git@") || remoteUrl.StartsWith("ssh://")) { if (webUrl.StartsWith("git@"))
remoteUrl = remoteUrl.Replace("ssh://", ""); {
remoteUrl = remoteUrl.Replace("git@", "https://"); webUrl = webUrl.Replace(":", "/").Replace("git@", "https://");
int firstColon = remoteUrl.IndexOf(':', 8);
if (firstColon != -1) remoteUrl = remoteUrl.Remove(firstColon, 1).Insert(firstColon, "/");
} }
if (remoteUrl.EndsWith(".git")) remoteUrl = remoteUrl.Substring(0, remoteUrl.Length - 4);
Application.OpenURL($"{remoteUrl}/commit/{hash}"); if (webUrl.EndsWith(".git"))
{
webUrl = webUrl.Substring(0, webUrl.Length - 4);
}
Application.OpenURL($"{webUrl}/commit/{hash}");
} }
private void CheckRepoStatus() private void CheckRepoStatus()
{ {
string projectPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); string projectPath = Path.GetFullPath(Path.Combine(Application.dataPath, ".."));
hasRepo = Directory.Exists(Path.Combine(projectPath, ".git")); hasRepo = Directory.Exists(Path.Combine(projectPath, ".git"));
if (hasRepo) {
if (hasRepo)
{
string output = RunGitCommand("status -s"); string output = RunGitCommand("status -s");
changedFiles = string.IsNullOrWhiteSpace(output) ? new string[0] : output.Split(new[] { '\n', '\r' }, System.StringSplitOptions.RemoveEmptyEntries); changedFiles = string.IsNullOrWhiteSpace(output) ? new string[0] : output.Split(new[] { '\n', '\r' }, System.StringSplitOptions.RemoveEmptyEntries);
FetchHistory(); FetchHistory();
} }
} }
private void FetchBranches()
{
string output = RunGitCommand("branch --format=\"%(refname:short)\"");
if (!string.IsNullOrWhiteSpace(output))
{
availableBranches = output.Split(new[] { '\n', '\r' }, System.StringSplitOptions.RemoveEmptyEntries);
selectedBranchIndex = System.Array.IndexOf(availableBranches, currentBranchName);
if (selectedBranchIndex == -1) selectedBranchIndex = 0;
}
}
private void FetchHistory() private void FetchHistory()
{ {
commitHistory.Clear(); commitHistory.Clear();
string output = RunGitCommand("log -n 25 --pretty=format:\"%h|%cd|%s\" --date=short"); string output = RunGitCommand("log -n 25 --pretty=format:\"%h|%cd|%s\" --date=short");
if (!string.IsNullOrWhiteSpace(output)) { if (!string.IsNullOrWhiteSpace(output))
foreach (string line in output.Split('\n')) { {
string[] p = line.Split('|'); string[] lines = output.Split(new[] { '\n', '\r' }, System.StringSplitOptions.RemoveEmptyEntries);
if (p.Length >= 3) commitHistory.Add(new CommitInfo { hash = p[0], date = p[1], message = p[2] }); foreach (string line in lines)
{
string[] parts = line.Split('|');
if (parts.Length >= 3)
{
commitHistory.Add(new CommitInfo { hash = parts[0], date = parts[1], message = parts[2] });
}
} }
} }
} }
private void PingAsset(string path) { private void PingAsset(string relativePath)
var obj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path); {
if (obj) { Selection.activeObject = obj; EditorGUIUtility.PingObject(obj); } UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(relativePath);
if (obj != null) { Selection.activeObject = obj; EditorGUIUtility.PingObject(obj); }
} }
private void GenerateUnityGitIgnore() { private void GenerateUnityGitIgnore()
{
string path = Path.Combine(Application.dataPath, "../.gitignore"); string path = Path.Combine(Application.dataPath, "../.gitignore");
if (!File.Exists(path)) File.WriteAllText(path, ".idea\n.vs\nbin\nobj\n/Library\n/Temp\n/UserSettings\n/Configs\n/*.csproj\n/*.sln\n/Logs\n/Packages/*\n!/Packages/manifest.json\n!/Packages/packages-lock.json\n~UnityDirMonSyncFile~*"); if (!File.Exists(path))
}
private void ExportPackageInventory()
{ {
string rootPath = Path.GetFullPath(Path.Combine(Application.dataPath, "..")); string content = @".idea
string outputPath = Path.Combine(rootPath, "PACKAGES.md"); .vs
bin
string unityManifest = Path.Combine(rootPath, "Packages", "manifest.json"); obj
string vpmManifest = Path.Combine(rootPath, "Packages", "vpm-manifest.json"); *.sln.DotSettings.user
/Library
List<string> mdLines = new List<string>(); /Temp
mdLines.Add("# 📦 Project Dependencies Inventory"); /UserSettings
mdLines.Add($"\n*Last Update: {System.DateTime.Now:yyyy-MM-dd HH:mm:ss}*"); /Configs
mdLines.Add("\n> [!TIP]\n> This list helps to restore the workspace if the Creator Companion or Unity fails to auto-resolve dependencies.\n"); /*.csproj
/*.sln
int totalFound = 0; /Logs
/Packages/*
totalFound += ParseManifest(unityManifest, "Unity Standard & Scoped Dependencies", mdLines); !/Packages/manifest.json
totalFound += ParseManifest(vpmManifest, "VRChat Package Manager (VPM) Dependencies", mdLines); !/Packages/packages-lock.json
!/Packages/vpm-manifest.json
try { ~UnityDirMonSyncFile~*";
File.WriteAllLines(outputPath, mdLines, System.Text.Encoding.UTF8); File.WriteAllText(path, content);
RunGitCommand($"add \"{outputPath}\""); UnityEngine.Debug.Log(".gitignore generated successfully!");
UnityEngine.Debug.Log($"Git-Tool: PACKAGES.md aktualisiert. {totalFound} Einträge gefunden.");
} catch (System.Exception e) {
UnityEngine.Debug.LogError("Git-Tool: Fehler beim Schreiben der PACKAGES.md: " + e.Message);
} }
} }
private int ParseManifest(string path, string sectionTitle, List<string> outputList) public static string RunGitCommand(string arguments)
{
try
{
ProcessStartInfo startInfo = new ProcessStartInfo("git", arguments)
{ {
if (!File.Exists(path)) return 0;
int count = 0;
try {
string content = File.ReadAllText(path);
// 1. Finde den Start des "dependencies" Blocks
int startIndex = content.IndexOf("\"dependencies\"");
if (startIndex == -1) return 0;
// Finde die erste öffnende Klammer nach dem Wort
startIndex = content.IndexOf("{", startIndex);
if (startIndex == -1) return 0;
// 2. Extrahiere exakt diesen Block, indem wir Klammern zählen
int openBraces = 0;
int endIndex = startIndex;
for (int i = startIndex; i < content.Length; i++) {
if (content[i] == '{') openBraces++;
if (content[i] == '}') {
openBraces--;
// Sobald wir wieder bei 0 sind, ist der Dependencies-Block geschlossen
if (openBraces == 0) {
endIndex = i;
break;
}
}
}
if (endIndex <= startIndex) return 0;
// Header nur zeichnen, wenn wir wirklich einen Block haben
outputList.Add($"## {sectionTitle}");
outputList.Add("| Package Name | Version / Source |");
outputList.Add("| :--- | :--- |");
// Den isolierten Block herauslösen und in Zeilen splitten
string dependenciesBlock = content.Substring(startIndex, endIndex - startIndex + 1);
string[] blockLines = dependenciesBlock.Split(new[] { '\r', '\n' }, System.StringSplitOptions.RemoveEmptyEntries);
string currentVpmPackage = "";
// 3. Den sauberen Block auswerten
foreach (string line in blockLines) {
string trimmed = line.Trim();
// Einzelne Klammern können wir ignorieren, da wir eh schon im richtigen Block sind
if (trimmed == "{" || trimmed == "}" || trimmed == "},") continue;
if (trimmed.StartsWith("\"")) {
string[] parts = trimmed.Split(new char[] { ':' }, 2);
if (parts.Length == 2) {
string key = parts[0].Replace("\"", "").Trim();
string rawValue = parts[1].Trim();
if (rawValue.StartsWith("{")) {
// VPM Paket Start (z.B. "com.vrchat.base": { )
currentVpmPackage = key;
}
else if (key == "version") {
// VPM Paket Version (z.B. "version": "3.10.2")
string val = rawValue.Replace("\"", "").Replace(",", "").Trim();
if (!string.IsNullOrEmpty(currentVpmPackage)) {
outputList.Add($"| `{currentVpmPackage}` | {val} |");
count++;
currentVpmPackage = "";
}
}
else if (!rawValue.StartsWith("{")) {
// Unity Flat Paket (z.B. "com.unity.timeline": "1.2.3")
string val = rawValue.Replace("\"", "").Replace(",", "").Trim();
outputList.Add($"| `{key}` | {val} |");
count++;
}
}
}
}
} catch (System.Exception e) {
UnityEngine.Debug.LogWarning($"Git-Tool: Warnung beim Lesen von {Path.GetFileName(path)}: {e.Message}");
}
if (count == 0) {
outputList.Add("| - | No entries found |");
}
outputList.Add(""); // Leerzeile für sauberes Markdown
return count;
}
// FIX: Methode ist wieder static!
public static string RunGitCommand(string args, bool logAction = false) {
try {
ProcessStartInfo si = new ProcessStartInfo("git", args) {
WorkingDirectory = Path.GetFullPath(Path.Combine(Application.dataPath, "..")), WorkingDirectory = Path.GetFullPath(Path.Combine(Application.dataPath, "..")),
UseShellExecute = false, UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
}; };
using (Process p = Process.Start(startInfo)) { p.WaitForExit(); return p.StandardOutput.ReadToEnd(); }
using (Process p = Process.Start(si)) {
string o = p.StandardOutput.ReadToEnd();
string e = p.StandardError.ReadToEnd();
p.WaitForExit();
string result = o + (string.IsNullOrWhiteSpace(e) ? "" : "\n" + e);
if (logAction) {
string time = System.DateTime.Now.ToString("HH:mm:ss");
string entry = $"[{time}] > git {args}\n";
if (!string.IsNullOrWhiteSpace(result)) entry += result.Trim() + "\n\n";
gitLogOutput = entry + gitLogOutput;
if (gitLogOutput.Length > 10000) gitLogOutput = gitLogOutput.Substring(0, 10000);
} }
catch { return ""; }
return result;
}
} catch { return ""; }
} }
} }
public class GitSaveListener : UnityEditor.AssetModificationProcessor public class GitSaveListener : UnityEditor.AssetModificationProcessor
{ {
public static string[] OnWillSaveAssets(string[] paths) { public static string[] OnWillSaveAssets(string[] paths)
EditorApplication.delayCall += () => { if (EditorWindow.HasOpenInstances<GitPanel>()) EditorWindow.GetWindow<GitPanel>("GIT Version Control System").RefreshData(); }; {
EditorApplication.delayCall += () =>
{
if (EditorWindow.HasOpenInstances<GitPanel>())
{
EditorWindow.GetWindow<GitPanel>().RefreshData();
}
};
return paths; return paths;
} }
} }
@@ -0,0 +1,16 @@
{
"name": "de.mrunknownde.gittool.Editor",
"rootNamespace": "",
"references": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"allowUnsafeCode": false,
"overrideReferences": false,
"precompiledReferences": [],
"autoReferenced": true,
"defineConstraints": [],
"versionDefines": [],
"noEngineReferences": false
}
-170
View File
@@ -1,170 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &11400000
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: c333ccfdd0cbdbc4ca30cef2dd6e6b9b, type: 3}
m_Name: ProTVRoomZone
m_EditorClassIdentifier:
serializedUdonProgramAsset: {fileID: 11400000, guid: 7f1b25a7785724b4a80dabaacffcc775,
type: 2}
udonAssembly:
assemblyError:
sourceCsScript: {fileID: 11500000, guid: 48ee0805fb47e2e4494f694b970678c1, type: 3}
scriptVersion: 2
compiledVersion: 2
behaviourSyncMode: 0
hasInteractEvent: 0
scriptID: -2239841375030423733
serializationData:
SerializedFormat: 2
SerializedBytes:
ReferencedUnityObjects: []
SerializedBytesString:
Prefab: {fileID: 0}
PrefabModificationsReferencedUnityObjects: []
PrefabModifications: []
SerializationNodes:
- Name: fieldDefinitions
Entry: 7
Data: 0|System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[UdonSharp.Compiler.FieldDefinition,
UdonSharp.Editor]], mscorlib
- Name: comparer
Entry: 7
Data: 1|System.Collections.Generic.GenericEqualityComparer`1[[System.String,
mscorlib]], mscorlib
- Name:
Entry: 8
Data:
- Name:
Entry: 12
Data: 2
- Name:
Entry: 7
Data:
- Name: $k
Entry: 1
Data: localVideoPlayer
- Name: $v
Entry: 7
Data: 2|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
- Name: <Name>k__BackingField
Entry: 1
Data: localVideoPlayer
- Name: <UserType>k__BackingField
Entry: 7
Data: 3|System.RuntimeType, mscorlib
- Name:
Entry: 1
Data: UnityEngine.GameObject, UnityEngine.CoreModule
- Name:
Entry: 8
Data:
- Name: <SystemType>k__BackingField
Entry: 9
Data: 3
- Name: <SyncMode>k__BackingField
Entry: 7
Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
- Name:
Entry: 6
Data:
- Name:
Entry: 8
Data:
- Name: <IsSerialized>k__BackingField
Entry: 5
Data: true
- Name: _fieldAttributes
Entry: 7
Data: 4|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
- Name:
Entry: 12
Data: 1
- Name:
Entry: 7
Data: 5|UnityEngine.HeaderAttribute, UnityEngine.CoreModule
- Name: header
Entry: 1
Data: "Der Videoplayer f\xFCr diesen Raum"
- Name:
Entry: 8
Data:
- Name:
Entry: 13
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 7
Data:
- Name: $k
Entry: 1
Data: roomCollider
- Name: $v
Entry: 7
Data: 6|UdonSharp.Compiler.FieldDefinition, UdonSharp.Editor
- Name: <Name>k__BackingField
Entry: 1
Data: roomCollider
- Name: <UserType>k__BackingField
Entry: 7
Data: 7|System.RuntimeType, mscorlib
- Name:
Entry: 1
Data: UnityEngine.BoxCollider, UnityEngine.PhysicsModule
- Name:
Entry: 8
Data:
- Name: <SystemType>k__BackingField
Entry: 9
Data: 7
- Name: <SyncMode>k__BackingField
Entry: 7
Data: System.Nullable`1[[UdonSharp.UdonSyncMode, UdonSharp.Runtime]], mscorlib
- Name:
Entry: 6
Data:
- Name:
Entry: 8
Data:
- Name: <IsSerialized>k__BackingField
Entry: 5
Data: false
- Name: _fieldAttributes
Entry: 7
Data: 8|System.Collections.Generic.List`1[[System.Attribute, mscorlib]], mscorlib
- Name:
Entry: 12
Data: 0
- Name:
Entry: 13
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 8
Data:
- Name:
Entry: 13
Data:
- Name:
Entry: 8
Data:
-157
View File
@@ -1,157 +0,0 @@
/*
* ============================================================================
* ProTV Room Zone Manager
* ============================================================================
* Ein autarkes Trigger-Modul zur ressourcenschonenden Steuerung von
* ProTV / AVPro Instanzen in VRChat
*
* written by MrUnknownDE
* https://mrunknown.de
* ============================================================================
*/
using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.Udon;
public class ProTVRoomZone : UdonSharpBehaviour
{
[Header("Der Videoplayer für diesen Raum")]
public GameObject localVideoPlayer;
[Space(10)]
[Header("Fade Settings")]
[Tooltip("Wie lange soll das Ein-/Ausblenden in Sekunden dauern?")]
public float fadeDuration = 1.5f;
[Tooltip("Ziehe hier die AudioSources des ProTVs rein - damit die Lautstärke smooth gefadet wird.")]
public AudioSource[] audioSources;
private BoxCollider roomCollider;
private float fadeProgress = 0f;
private int fadeState = 0;
// Hier speichern wir die echte Lautstärke, bevor sie verfälscht wird
private float[] savedVolumes;
void Start()
{
roomCollider = GetComponent<BoxCollider>();
// Arrays initialisieren und Basis-Lautstärke beim Start sichern
if (audioSources != null && audioSources.Length > 0)
{
savedVolumes = new float[audioSources.Length];
for (int i = 0; i < audioSources.Length; i++)
{
if (audioSources[i] != null) savedVolumes[i] = audioSources[i].volume;
}
}
SendCustomEventDelayedSeconds(nameof(CheckSpawnPosition), 2.0f);
}
public void CheckSpawnPosition()
{
VRCPlayerApi player = Networking.LocalPlayer;
if (!Utilities.IsValid(player)) return;
if (roomCollider != null && roomCollider.bounds.Contains(player.GetPosition()))
{
if (localVideoPlayer != null) localVideoPlayer.SetActive(true);
fadeProgress = 1f;
}
else
{
UpdateSavedVolumes();
if (localVideoPlayer != null) localVideoPlayer.SetActive(false);
fadeProgress = 0f;
}
}
public override void OnPlayerTriggerEnter(VRCPlayerApi player)
{
if (!Utilities.IsValid(player) || !player.isLocal) return;
if (localVideoPlayer != null) localVideoPlayer.SetActive(true);
fadeProgress = 0f;
fadeState = 1;
}
public override void OnPlayerTriggerExit(VRCPlayerApi player)
{
if (!Utilities.IsValid(player) || !player.isLocal) return;
UpdateSavedVolumes();
fadeProgress = 1f;
fadeState = -1; // Starte Fade Out
}
void Update()
{
if (fadeState == 0) return;
if (fadeState == 1) // FADE IN
{
fadeProgress += Time.deltaTime / fadeDuration;
if (fadeProgress >= 1f)
{
fadeProgress = 1f;
fadeState = 0;
}
ApplyFadedVolume();
}
else if (fadeState == -1) // FADE OUT
{
fadeProgress -= Time.deltaTime / fadeDuration;
if (fadeProgress <= 0f)
{
fadeProgress = 0f;
fadeState = 0;
// DER PRO-TV FIX:
RestoreOriginalVolume();
if (localVideoPlayer != null) localVideoPlayer.SetActive(false);
}
else
{
ApplyFadedVolume();
}
}
}
private void UpdateSavedVolumes()
{
if (audioSources == null) return;
for (int i = 0; i < audioSources.Length; i++)
{
if (audioSources[i] != null && audioSources[i].volume > 0.05f)
{
savedVolumes[i] = audioSources[i].volume;
}
}
}
private void ApplyFadedVolume()
{
if (audioSources == null || savedVolumes == null) return;
for (int i = 0; i < audioSources.Length; i++)
{
if (audioSources[i] != null)
{
audioSources[i].volume = savedVolumes[i] * fadeProgress;
}
}
}
private void RestoreOriginalVolume()
{
if (audioSources == null || savedVolumes == null) return;
for (int i = 0; i < audioSources.Length; i++)
{
if (audioSources[i] != null)
{
audioSources[i].volume = savedVolumes[i];
}
}
}
}
+19 -65
View File
@@ -1,73 +1,27 @@
# 🛠️ MrUnknownDE VRChat Unity Tools # 🛠️ Unity Git Control Tool (VRChat Ready)
Welcome to the **MrUnknownDE VRChat Unity Tools** repository. This is a collection of customized, lightweight, and high-performance Unity Editor tools designed specifically to improve the workflow of VRChat World Creators. A lightweight, integrated Source Control Panel built directly into Unity. Designed to eliminate the constant context-switching between the Unity Editor and external command-line tools. Perfectly tailored for VRChat World Creators and developers who want to maintain clean version control without the bloat.
Instead of dealing with standalone applications or command-line interfaces, these tools bring essential DevOps and social features directly into your Unity Editor. ## ✨ Features
- **One-Click Init:** Initializes a new repository and automatically generates a clean Unity `.gitignore` file.
---
## 📦 Current Tools in this Package
### 1. Git Version Control System
A fully integrated Source Control Panel built directly into Unity. No more context-switching between the editor and external Git clients.
- **Smart Initialization:** Enter a Gitea/GitHub remote URL, and the tool will automatically handle the `init`, branch setup, and pull/merge existing server data before pushing your local project.
- **VS Code Style Interface:** Compact overview of modified, added, deleted, and untracked files. - **VS Code Style Interface:** Compact overview of modified, added, deleted, and untracked files.
- **Auto-Save Hook:** Pressing `CTRL+S` in Unity or changing focus automatically refreshes the Git status. - **Auto-Timestamp Commits:** If you don't provide a custom commit message, the tool gracefully falls back to a clean timestamp format.
- **Timestamp Commits:** If you don't provide a custom commit message, the tool gracefully falls back to a clean timestamp format. - **Interactive File Explorer:** - `Single Click` on a file -> Pings and focuses the asset in the Unity Project View.
- **Interactive File Explorer:** Double-click any file to open the built-in Code Diff Viewer right inside the Editor. - `Double Click` on a file -> Opens the built-in Code Diff Viewer right inside the Editor.
- **Revert (Panic Button):** Easily discard all uncommitted changes if an experiment goes wrong. - **History View:** Browse your latest commits. Click any commit to open it directly in your remote web view (Gitea, GitHub, GitLab).
### 2. Discord Rich Presence (RPC) ## 🛠️ Manual Installation
Let your community know what you are working on without saying a word. 1. Download the latest version as a `.zip` archive.
- **Live Status:** Shows your current active Unity scene directly on your Discord profile. 2. Extract the folder.
- **Privacy Mode:** Hide the scene name if you are working on an unannounced or secret project. 3. Place the folder directly into your Unity project's `Packages` directory.
- **Custom Status:** Add custom text (e.g., "Baking Lightmaps..." or "Writing Udon Scripts") to your Discord activity. *Alternative:* Copy the `.cs` files from the `Editor` folder into any `Editor` folder inside your `Assets` directory.
---
## ⚖️ ⚠️ Achtung: Law & Order (The German Way)
Now it's getting serious—or as we say in Germany: **"Jetzt wird es deutsch!"** 🇩🇪
### The "Assets Folder" Rule
Listen up, because the German Copyright Law (*Urheberrechtsgesetz*) doesn't take jokes lightly.
**DO NOT upload your paid Assets, Store-bought Plugins, or copyrighted Prefabs into a PUBLIC Repository.** If you distribute copyrighted material from creators without permission:
- **Civil Law:** You could face fines (Schadensersatz) that will make your bank account cry.
- **Criminal Law:** According to § 106 UrhG, unauthorized exploitation of copyrighted works can lead to **up to 3 years in prison** or heavy fines.
**Pro-Tip:** Always use a **Private Repository** (e.g., on a private Gitea server or a private GitHub repo) if your project contains paid assets. Your wallet and your freedom will thank you. Don't let the "Abmahnanwalt" be your first beta tester ;)
---
## 🚀 Installation
This tool is installed manually directly into your Unity project.
### Direct Folder Drop
1. Download the latest `mrunknownde-vcc-tools-v20xx-xx-xx.zip` release.
2. Extract the archive.
3. Drag and drop the extracted folder directly into the `Assets` directory inside your Unity project's root folder (using Windows Explorer / File Explorer, not inside the Unity Editor window). Unity will automatically compile the tools.
---
## 🕹️ Usage ## 🕹️ Usage
Once installed, open the tool via the top menu bar in Unity:
`Tools` -> `Git-Tool`
Once installed, you can access the tools via the top menu bar in Unity: A floating window will appear. You can easily dock this window into your custom layout (e.g., right next to the Inspector).
`Tools` -> `MrUnknownDE` -> `GIT Version Control`<br> ## ⚠️ Prerequisites
`Tools` -> `MrUnknownDE` -> `Discord RPC` - **Git** must be installed on your system and added to your global environment variables (`PATH`).
- For automatic pushes to Gitea/GitHub to work seamlessly, you should have **SSH keys** or cached credentials configured. Unity cannot intercept terminal password prompts.
The tools will open as floating windows. You can easily dock them into your custom Unity layout (e.g., right next to the Inspector or Console).
---
## ⚠️ Prerequisites & Troubleshooting
- **Git Installation:** You must have Git installed on your Windows machine. If the tool does not detect Git, it will provide a download link within the Unity UI.
- **Environment Variables:** If you just installed Git, **you must completely restart Unity Hub and the Unity Editor** so Windows can load the new `PATH` variables.
- **Authentication:** For automatic pushes to remote servers (Gitea/GitHub) to work seamlessly, ensure you have SSH keys or cached Git credentials configured on your system. Unity cannot intercept terminal password prompts.
---
*Built with ❤️ for the VRChat Community.*