개발자용 멀티플레이 기능 추가

This commit is contained in:
2026-02-02 20:26:28 +09:00
parent b4c22edcbd
commit 9dea9daaa9
23 changed files with 1754 additions and 13 deletions

View File

@@ -0,0 +1,176 @@
using UnityEngine;
using UnityEditor;
using Unity.Netcode;
namespace Northbound.Editor
{
[CustomEditor(typeof(NetworkConnectionHelper))]
public class NetworkConnectionHelperEditor : UnityEditor.Editor
{
private NetworkConnectionHelper _helper;
private GUIStyle _statusStyle;
private void OnEnable()
{
_helper = (NetworkConnectionHelper)target;
}
public override void OnInspectorGUI()
{
_statusStyle = new GUIStyle(EditorStyles.boldLabel);
_statusStyle.alignment = TextAnchor.MiddleCenter;
_statusStyle.fontSize = 12;
DrawDefaultInspector();
EditorGUILayout.Space(10);
DrawConnectionControls();
EditorGUILayout.Space(10);
DrawStatus();
EditorGUILayout.Space(10);
DrawQuickActions();
}
private void DrawConnectionControls()
{
EditorGUILayout.LabelField("Network Controls", EditorStyles.boldLabel);
EditorGUI.BeginDisabledGroup(IsConnected());
if (GUILayout.Button("Start Host", GUILayout.Height(30)))
{
_helper.StartHost();
}
if (GUILayout.Button("Start Server", GUILayout.Height(30)))
{
_helper.StartServer();
}
if (GUILayout.Button("Start Client", GUILayout.Height(30)))
{
_helper.StartClient();
}
EditorGUI.EndDisabledGroup();
EditorGUI.BeginDisabledGroup(!IsConnected());
if (GUILayout.Button("Disconnect", GUILayout.Height(30)))
{
_helper.Disconnect();
}
EditorGUI.EndDisabledGroup();
}
private void DrawStatus()
{
EditorGUILayout.LabelField("Status", EditorStyles.boldLabel);
string status = GetDetailedStatus();
Color bgColor = GetStatusColor();
var oldBgColor = GUI.backgroundColor;
GUI.backgroundColor = bgColor;
EditorGUILayout.LabelField(status, _statusStyle, GUILayout.Height(25));
GUI.backgroundColor = oldBgColor;
if (NetworkManager.Singleton != null && IsConnected())
{
DrawNetworkInfo();
}
}
private void DrawNetworkInfo()
{
EditorGUILayout.LabelField("Network Information", EditorStyles.boldLabel);
using (new EditorGUILayout.VerticalScope(EditorStyles.helpBox))
{
if (NetworkManager.Singleton.IsHost)
{
EditorGUILayout.LabelField($"Mode: Host");
}
else if (NetworkManager.Singleton.IsServer)
{
EditorGUILayout.LabelField($"Mode: Server");
}
else if (NetworkManager.Singleton.IsClient)
{
EditorGUILayout.LabelField($"Mode: Client");
}
EditorGUILayout.LabelField($"Connected Clients: {NetworkManager.Singleton.ConnectedClients.Count}");
EditorGUILayout.LabelField($"Local Client ID: {NetworkManager.Singleton.LocalClientId}");
var transport = NetworkManager.Singleton.GetComponent<Unity.Netcode.Transports.UTP.UnityTransport>();
if (transport != null)
{
EditorGUILayout.LabelField($"Address: {transport.ConnectionData.Address}");
EditorGUILayout.LabelField($"Port: {transport.ConnectionData.Port}");
}
}
}
private void DrawQuickActions()
{
EditorGUILayout.LabelField("Quick Actions", EditorStyles.boldLabel);
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Open Connection Window"))
{
NetworkConnectionWindow.ShowWindow();
}
if (GUILayout.Button("Test Connection"))
{
TestConnection();
}
EditorGUILayout.EndHorizontal();
}
private bool IsConnected()
{
return NetworkManager.Singleton != null &&
(NetworkManager.Singleton.IsClient || NetworkManager.Singleton.IsServer);
}
private string GetDetailedStatus()
{
if (NetworkManager.Singleton == null)
return "NetworkManager Not Found";
if (NetworkManager.Singleton.IsHost)
return "HOSTING";
if (NetworkManager.Singleton.IsServer)
return "SERVER RUNNING";
if (NetworkManager.Singleton.IsClient)
return "CONNECTED";
return "NOT CONNECTED";
}
private Color GetStatusColor()
{
if (NetworkManager.Singleton == null)
return new Color(0.8f, 0.3f, 0.3f);
if (NetworkManager.Singleton.IsHost || NetworkManager.Singleton.IsClient || NetworkManager.Singleton.IsServer)
return new Color(0.3f, 0.8f, 0.3f);
return new Color(0.8f, 0.6f, 0.3f);
}
private void TestConnection()
{
Debug.Log($"[NetworkConnectionHelper] Connection test - Status: {_helper.GetStatus()}");
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 36e75e477fa8d69459450bd36b5c6878

View File

@@ -0,0 +1,295 @@
using UnityEngine;
using UnityEditor;
using Unity.Netcode;
using System.IO;
using UnityEngine.SceneManagement;
namespace Northbound.Editor
{
public class NetworkConnectionWindow : UnityEditor.EditorWindow
{
private string _serverIP = "127.0.0.1";
private string _port = "7777";
private bool _isHost = true;
private NetworkConnectionMode _connectionMode = NetworkConnectionMode.Host;
private string _savedSettingsPath;
private enum NetworkConnectionMode
{
Host,
Client,
Server
}
[MenuItem("Window/Network/Connection Manager %#n")]
public static void ShowWindow()
{
var window = GetWindow<NetworkConnectionWindow>("Network Connection");
window.minSize = new Vector2(350, 250);
}
private void OnEnable()
{
_savedSettingsPath = Path.Combine(Application.persistentDataPath, "NetworkConnectionSettings.json");
LoadSettings();
}
private void OnGUI()
{
EditorGUILayout.Space(10);
GUILayout.Label("Network Connection Manager", EditorStyles.boldLabel);
EditorGUILayout.Space(5);
DrawConnectionModeUI();
EditorGUILayout.Space(10);
DrawConnectionSettings();
EditorGUILayout.Space(10);
DrawStatusDisplay();
EditorGUILayout.Space(10);
DrawActionButtons();
EditorGUILayout.Space(10);
DrawSettingsButtons();
}
private void DrawConnectionModeUI()
{
EditorGUILayout.LabelField("Connection Mode", EditorStyles.boldLabel);
_connectionMode = (NetworkConnectionMode)EditorGUILayout.EnumPopup("Mode", _connectionMode);
if (_connectionMode == NetworkConnectionMode.Host)
{
_isHost = true;
}
}
private void DrawConnectionSettings()
{
EditorGUILayout.LabelField("Connection Settings", EditorStyles.boldLabel);
if (_connectionMode == NetworkConnectionMode.Client)
{
_serverIP = EditorGUILayout.TextField("Server IP", _serverIP);
_port = EditorGUILayout.TextField("Port", _port);
}
else if (_connectionMode == NetworkConnectionMode.Host || _connectionMode == NetworkConnectionMode.Server)
{
_port = EditorGUILayout.TextField("Port", _port);
}
if (GUI.changed)
{
SaveSettings();
}
}
private void DrawStatusDisplay()
{
EditorGUILayout.LabelField("Status", EditorStyles.boldLabel);
string status = GetNetworkStatus();
Color statusColor = GetStatusColor();
var oldColor = GUI.color;
GUI.color = statusColor;
EditorGUILayout.LabelField(status, EditorStyles.helpBox);
GUI.color = oldColor;
}
private void DrawActionButtons()
{
EditorGUI.BeginDisabledGroup(IsNetworkActive());
if (GUILayout.Button(_connectionMode == NetworkConnectionMode.Client ? "Connect" : "Start", GUILayout.Height(30)))
{
StartConnection();
}
EditorGUI.EndDisabledGroup();
EditorGUI.BeginDisabledGroup(!IsNetworkActive());
if (GUILayout.Button("Disconnect", GUILayout.Height(30)))
{
Disconnect();
}
EditorGUI.EndDisabledGroup();
}
private void DrawSettingsButtons()
{
EditorGUILayout.BeginHorizontal();
if (GUILayout.Button("Save Settings"))
{
SaveSettings();
Debug.Log("[NetworkConnectionWindow] Settings saved.");
}
if (GUILayout.Button("Load Settings"))
{
LoadSettings();
Debug.Log("[NetworkConnectionWindow] Settings loaded.");
}
EditorGUILayout.EndHorizontal();
}
private void StartConnection()
{
if (NetworkManager.Singleton == null)
{
EditorUtility.DisplayDialog("Error", "NetworkManager not found in the scene!", "OK");
return;
}
ushort port;
if (!ushort.TryParse(_port, out port))
{
EditorUtility.DisplayDialog("Error", "Invalid port number!", "OK");
return;
}
NetworkManager.Singleton.GetComponent<Unity.Netcode.Transports.UTP.UnityTransport>()?.SetConnectionData(
_serverIP,
port
);
switch (_connectionMode)
{
case NetworkConnectionMode.Host:
NetworkManager.Singleton.StartHost();
Debug.Log($"[NetworkConnectionWindow] Started Host on port {port}");
break;
case NetworkConnectionMode.Client:
NetworkManager.Singleton.StartClient();
Debug.Log($"[NetworkConnectionWindow] Started Client connecting to {_serverIP}:{port}");
break;
case NetworkConnectionMode.Server:
NetworkManager.Singleton.StartServer();
Debug.Log($"[NetworkConnectionWindow] Started Server on port {port}");
break;
}
}
private void Disconnect()
{
if (NetworkManager.Singleton != null)
{
NetworkManager.Singleton.Shutdown();
Debug.Log("[NetworkConnectionWindow] Disconnected");
}
}
private bool IsNetworkActive()
{
return NetworkManager.Singleton != null &&
(NetworkManager.Singleton.IsClient || NetworkManager.Singleton.IsServer);
}
private string GetNetworkStatus()
{
if (NetworkManager.Singleton == null)
return "NetworkManager not found";
if (NetworkManager.Singleton.IsHost)
return $"Hosting (Port: {GetActivePort()})";
if (NetworkManager.Singleton.IsServer)
return $"Server (Port: {GetActivePort()})";
if (NetworkManager.Singleton.IsClient)
return $"Client connected to {GetActiveIP()}:{GetActivePort()}";
return "Not connected";
}
private Color GetStatusColor()
{
if (NetworkManager.Singleton == null)
return Color.red;
if (NetworkManager.Singleton.IsHost || NetworkManager.Singleton.IsClient || NetworkManager.Singleton.IsServer)
return new Color(0f, 0.7f, 0f);
return new Color(1f, 0.7f, 0f);
}
private string GetActiveIP()
{
if (NetworkManager.Singleton == null) return "N/A";
var transport = NetworkManager.Singleton.GetComponent<Unity.Netcode.Transports.UTP.UnityTransport>();
if (transport != null)
{
return transport.ConnectionData.Address;
}
return "N/A";
}
private string GetActivePort()
{
if (NetworkManager.Singleton == null) return "N/A";
var transport = NetworkManager.Singleton.GetComponent<Unity.Netcode.Transports.UTP.UnityTransport>();
if (transport != null)
{
return transport.ConnectionData.Port.ToString();
}
return "N/A";
}
private void SaveSettings()
{
try
{
string json = $"{{\"serverIP\":\"{_serverIP}\",\"port\":\"{_port}\",\"connectionMode\":{_connectionMode}}}";
File.WriteAllText(_savedSettingsPath, json);
}
catch (System.Exception e)
{
Debug.LogError($"[NetworkConnectionWindow] Failed to save settings: {e.Message}");
}
}
private void LoadSettings()
{
try
{
if (File.Exists(_savedSettingsPath))
{
string json = File.ReadAllText(_savedSettingsPath);
var settings = JsonUtility.FromJson<ConnectionSettings>(json);
_serverIP = settings.serverIP;
_port = settings.port;
_connectionMode = (NetworkConnectionMode)settings.connectionMode;
}
}
catch (System.Exception e)
{
Debug.LogError($"[NetworkConnectionWindow] Failed to load settings: {e.Message}");
}
}
private void Update()
{
if (IsNetworkActive())
{
Repaint();
}
}
[System.Serializable]
private class ConnectionSettings
{
public string serverIP;
public string port;
public int connectionMode;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b7b2610829b9bba4f801fb9d527963c9

View File

@@ -0,0 +1,176 @@
using UnityEngine;
using UnityEngine.UI;
using UnityEditor;
namespace Northbound.Editor
{
public class NetworkUIBuilder
{
[MenuItem("GameObject/Network/Create Network Join UI")]
public static void CreateNetworkJoinUI()
{
Canvas canvas = FindOrCreateCanvas();
GameObject panel = CreatePanel(canvas.transform);
InputField ipInput = CreateInputField(panel.transform, "IP Input", "127.0.0.1", 150);
InputField portInput = CreateInputField(panel.transform, "Port Input", "7777", 100);
Button joinButton = CreateButton(panel.transform, "Join", new Vector2(0, -80));
Button hostButton = CreateButton(panel.transform, "Host", new Vector2(-60, -80));
Button disconnectButton = CreateButton(panel.transform, "Disconnect", new Vector2(60, -80));
Text statusText = CreateStatusText(panel.transform);
NetworkJoinUI networkJoinUI = panel.AddComponent<NetworkJoinUI>();
networkJoinUI.SetUIReferences(panel, ipInput, portInput, joinButton, hostButton, disconnectButton, statusText);
Selection.activeGameObject = panel;
Debug.Log("[NetworkUIBuilder] Network Join UI created!");
}
private static Canvas FindOrCreateCanvas()
{
Canvas[] canvases = Object.FindObjectsByType<Canvas>(FindObjectsSortMode.None);
if (canvases.Length > 0)
{
return canvases[0];
}
GameObject canvasObj = new GameObject("NetworkCanvas");
Canvas canvas = canvasObj.AddComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvasObj.AddComponent<CanvasScaler>();
canvasObj.AddComponent<GraphicRaycaster>();
return canvas;
}
private static GameObject CreatePanel(Transform parent)
{
GameObject panel = new GameObject("NetworkJoinPanel");
panel.transform.SetParent(parent, false);
RectTransform rect = panel.AddComponent<RectTransform>();
rect.anchorMin = new Vector2(0.5f, 0.5f);
rect.anchorMax = new Vector2(0.5f, 0.5f);
rect.pivot = new Vector2(0.5f, 0.5f);
rect.sizeDelta = new Vector2(300, 250);
rect.anchoredPosition = Vector2.zero;
Image image = panel.AddComponent<Image>();
image.color = new Color(0.1f, 0.1f, 0.1f, 0.95f);
return panel;
}
private static InputField CreateInputField(Transform parent, string name, string placeholder, float width)
{
GameObject fieldObj = new GameObject(name);
fieldObj.transform.SetParent(parent, false);
RectTransform rect = fieldObj.AddComponent<RectTransform>();
rect.anchorMin = new Vector2(0.5f, 0.5f);
rect.anchorMax = new Vector2(0.5f, 0.5f);
rect.pivot = new Vector2(0.5f, 0.5f);
rect.sizeDelta = new Vector2(width, 30);
int yPos = name.Contains("IP") ? 80 : 40;
rect.anchoredPosition = new Vector2(0, yPos);
Image bgImage = fieldObj.AddComponent<Image>();
bgImage.color = new Color(0.2f, 0.2f, 0.2f, 1f);
InputField inputField = fieldObj.AddComponent<InputField>();
inputField.textComponent = CreateTextComponent(fieldObj.transform, "");
inputField.text = placeholder;
inputField.contentType = InputField.ContentType.Standard;
Text placeholderText = CreateTextComponent(fieldObj.transform, placeholder);
placeholderText.color = new Color(0.5f, 0.5f, 0.5f, 0.5f);
inputField.placeholder = placeholderText;
Text label = CreateTextComponent(parent, name.ToUpper());
RectTransform labelRect = label.GetComponent<RectTransform>();
labelRect.anchoredPosition = new Vector2(-width / 2 - 30, yPos);
return inputField;
}
private static Button CreateButton(Transform parent, string text, Vector2 position)
{
GameObject buttonObj = new GameObject(text + "Button");
buttonObj.transform.SetParent(parent, false);
RectTransform rect = buttonObj.AddComponent<RectTransform>();
rect.anchorMin = new Vector2(0.5f, 0.5f);
rect.anchorMax = new Vector2(0.5f, 0.5f);
rect.pivot = new Vector2(0.5f, 0.5f);
rect.sizeDelta = new Vector2(80, 30);
rect.anchoredPosition = position;
Image bgImage = buttonObj.AddComponent<Image>();
bgImage.color = new Color(0.3f, 0.5f, 0.8f, 1f);
Button button = buttonObj.AddComponent<Button>();
Text buttonText = CreateTextComponent(buttonObj.transform, text);
buttonText.alignment = TextAnchor.MiddleCenter;
return button;
}
private static Text CreateTextComponent(Transform parent, string text)
{
GameObject textObj = new GameObject("Text");
textObj.transform.SetParent(parent, false);
RectTransform rect = textObj.AddComponent<RectTransform>();
rect.anchorMin = Vector2.zero;
rect.anchorMax = Vector2.one;
rect.offsetMin = Vector2.zero;
rect.offsetMax = Vector2.zero;
Text textComponent = textObj.AddComponent<Text>();
textComponent.text = text;
textComponent.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
textComponent.fontSize = 14;
textComponent.color = Color.white;
textComponent.alignment = TextAnchor.MiddleCenter;
return textComponent;
}
private static Text CreateStatusText(Transform parent)
{
GameObject textObj = new GameObject("StatusText");
textObj.transform.SetParent(parent, false);
RectTransform rect = textObj.AddComponent<RectTransform>();
rect.anchorMin = new Vector2(0.5f, 0.5f);
rect.anchorMax = new Vector2(0.5f, 0.5f);
rect.pivot = new Vector2(0.5f, 0.5f);
rect.sizeDelta = new Vector2(280, 30);
rect.anchoredPosition = new Vector2(0, -120);
Text textComponent = textObj.AddComponent<Text>();
textComponent.text = "Not connected";
textComponent.font = Resources.GetBuiltinResource<Font>("Arial.ttf");
textComponent.fontSize = 12;
textComponent.color = Color.yellow;
textComponent.alignment = TextAnchor.MiddleCenter;
return textComponent;
}
[MenuItem("GameObject/Network/Create Network Helper")]
public static void CreateNetworkHelper()
{
GameObject helperObj = new GameObject("NetworkConnectionHelper");
helperObj.AddComponent<NetworkConnectionHelper>();
Selection.activeGameObject = helperObj;
Debug.Log("[NetworkUIBuilder] Network Connection Helper created!");
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 010611d682d1a064caa47bc3667f0675

View File

@@ -0,0 +1,93 @@
using UnityEngine;
using UnityEditor;
namespace Northbound.Editor
{
public class QuickNetworkSetupEditor
{
[MenuItem("Tools/Network/Quick Setup for IP Connection")]
public static void QuickSetup()
{
bool disableAutoHost = EditorUtility.DisplayDialog(
"Quick Network Setup",
"This will set up your scene for manual IP-based network connections.\n\n" +
"It will:\n" +
"- Create a NetworkConnectionHelper component\n" +
"- Disable AutoHost (to prevent auto-start)\n\n" +
"Do you want to disable AutoHost?",
"Yes, disable AutoHost",
"No, keep AutoHost"
);
CreateNetworkHelper();
if (disableAutoHost)
{
DisableAutoHost();
}
EditorUtility.DisplayDialog(
"Setup Complete",
"Quick network setup complete!\n\n" +
"Next steps:\n" +
"1. Open Window > Network > Connection Manager\n" +
"2. Or use the NetworkConnectionHelper in Inspector\n" +
"3. Start Host on one instance\n" +
"4. Start Client on other instances with the Host's IP",
"OK"
);
}
[MenuItem("Tools/Network/Create Connection Helper")]
public static void CreateNetworkHelper()
{
if (Object.FindObjectOfType<NetworkConnectionHelper>() == null)
{
GameObject helperObj = new GameObject("NetworkConnectionHelper");
helperObj.AddComponent<NetworkConnectionHelper>();
Selection.activeGameObject = helperObj;
Debug.Log("[QuickNetworkSetup] NetworkConnectionHelper created");
}
else
{
Debug.Log("[QuickNetworkSetup] NetworkConnectionHelper already exists");
}
}
[MenuItem("Tools/Network/Disable AutoHost")]
public static void DisableAutoHost()
{
AutoHost[] autoHosts = Object.FindObjectsByType<AutoHost>(FindObjectsSortMode.None);
if (autoHosts.Length > 0)
{
foreach (var autoHost in autoHosts)
{
autoHost.enabled = false;
}
Debug.Log($"[QuickNetworkSetup] Disabled {autoHosts.Length} AutoHost component(s)");
}
else
{
Debug.Log("[QuickNetworkSetup] No AutoHost components found");
}
}
[MenuItem("Tools/Network/Enable AutoHost")]
public static void EnableAutoHost()
{
AutoHost[] autoHosts = Object.FindObjectsByType<AutoHost>(FindObjectsSortMode.None);
if (autoHosts.Length > 0)
{
foreach (var autoHost in autoHosts)
{
autoHost.enabled = true;
}
Debug.Log($"[QuickNetworkSetup] Enabled {autoHosts.Length} AutoHost component(s)");
}
else
{
Debug.Log("[QuickNetworkSetup] No AutoHost components found");
}
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d84f5c07a6e69614dad5178e0f994335