From 9dea9daaa9b6088388dbeef21532bcf26bb3d83a Mon Sep 17 00:00:00 2001 From: dal4segno Date: Mon, 2 Feb 2026 20:26:28 +0900 Subject: [PATCH] =?UTF-8?q?=EA=B0=9C=EB=B0=9C=EC=9E=90=EC=9A=A9=20?= =?UTF-8?q?=EB=A9=80=ED=8B=B0=ED=94=8C=EB=A0=88=EC=9D=B4=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assembly-CSharp-Editor.csproj | 4 + Assembly-CSharp.csproj | 3 + Assets/Scripts/AutoHost.cs | 40 ++- .../Editor/NetworkConnectionHelperEditor.cs | 176 +++++++++++ .../NetworkConnectionHelperEditor.cs.meta | 2 + .../Scripts/Editor/NetworkConnectionWindow.cs | 295 ++++++++++++++++++ .../Editor/NetworkConnectionWindow.cs.meta | 2 + Assets/Scripts/Editor/NetworkUIBuilder.cs | 176 +++++++++++ .../Scripts/Editor/NetworkUIBuilder.cs.meta | 2 + .../Scripts/Editor/QuickNetworkSetupEditor.cs | 93 ++++++ .../Editor/QuickNetworkSetupEditor.cs.meta | 2 + Assets/Scripts/NETWORK_CONNECTION_GUIDE_KO.md | 176 +++++++++++ .../NETWORK_CONNECTION_GUIDE_KO.md.meta | 7 + Assets/Scripts/NETWORK_CONNECTION_README.md | 137 ++++++++ .../Scripts/NETWORK_CONNECTION_README.md.meta | 7 + Assets/Scripts/NETWORK_CONNECTION_SUMMARY.md | 192 ++++++++++++ .../NETWORK_CONNECTION_SUMMARY.md.meta | 7 + Assets/Scripts/NetworkConnectionHelper.cs | 117 +++++++ .../Scripts/NetworkConnectionHelper.cs.meta | 2 + Assets/Scripts/NetworkJoinUI.cs | 269 ++++++++++++++++ Assets/Scripts/NetworkJoinUI.cs.meta | 2 + Assets/Scripts/QuickNetworkSetup.cs | 54 ++++ Assets/Scripts/QuickNetworkSetup.cs.meta | 2 + 23 files changed, 1754 insertions(+), 13 deletions(-) create mode 100644 Assets/Scripts/Editor/NetworkConnectionHelperEditor.cs create mode 100644 Assets/Scripts/Editor/NetworkConnectionHelperEditor.cs.meta create mode 100644 Assets/Scripts/Editor/NetworkConnectionWindow.cs create mode 100644 Assets/Scripts/Editor/NetworkConnectionWindow.cs.meta create mode 100644 Assets/Scripts/Editor/NetworkUIBuilder.cs create mode 100644 Assets/Scripts/Editor/NetworkUIBuilder.cs.meta create mode 100644 Assets/Scripts/Editor/QuickNetworkSetupEditor.cs create mode 100644 Assets/Scripts/Editor/QuickNetworkSetupEditor.cs.meta create mode 100644 Assets/Scripts/NETWORK_CONNECTION_GUIDE_KO.md create mode 100644 Assets/Scripts/NETWORK_CONNECTION_GUIDE_KO.md.meta create mode 100644 Assets/Scripts/NETWORK_CONNECTION_README.md create mode 100644 Assets/Scripts/NETWORK_CONNECTION_README.md.meta create mode 100644 Assets/Scripts/NETWORK_CONNECTION_SUMMARY.md create mode 100644 Assets/Scripts/NETWORK_CONNECTION_SUMMARY.md.meta create mode 100644 Assets/Scripts/NetworkConnectionHelper.cs create mode 100644 Assets/Scripts/NetworkConnectionHelper.cs.meta create mode 100644 Assets/Scripts/NetworkJoinUI.cs create mode 100644 Assets/Scripts/NetworkJoinUI.cs.meta create mode 100644 Assets/Scripts/QuickNetworkSetup.cs create mode 100644 Assets/Scripts/QuickNetworkSetup.cs.meta diff --git a/Assembly-CSharp-Editor.csproj b/Assembly-CSharp-Editor.csproj index 0b04632..e20c515 100644 --- a/Assembly-CSharp-Editor.csproj +++ b/Assembly-CSharp-Editor.csproj @@ -49,10 +49,12 @@ + + @@ -65,7 +67,9 @@ + + diff --git a/Assembly-CSharp.csproj b/Assembly-CSharp.csproj index 8a7d6b3..8e4cb8b 100644 --- a/Assembly-CSharp.csproj +++ b/Assembly-CSharp.csproj @@ -86,6 +86,7 @@ + @@ -94,6 +95,7 @@ + @@ -120,6 +122,7 @@ + diff --git a/Assets/Scripts/AutoHost.cs b/Assets/Scripts/AutoHost.cs index 8f51ca7..9935fa6 100644 --- a/Assets/Scripts/AutoHost.cs +++ b/Assets/Scripts/AutoHost.cs @@ -3,24 +3,38 @@ using Unity.Netcode; public class AutoHost : MonoBehaviour { - // 에디터에서만 작동하도록 설정 - void Start() + [Header("Auto Host Settings")] + [SerializeField] private bool enableAutoHost = true; + [SerializeField] private bool onlyInEditor = true; + + private void Start() { -#if UNITY_EDITOR - // 1. NetworkManager가 씬에 존재하는지 확인 - if (NetworkManager.Singleton != null) + if (ShouldAutoStart()) { - // 2. 이미 서버나 클라이언트가 실행 중이 아닐 때만 실행 - if (!NetworkManager.Singleton.IsServer && !NetworkManager.Singleton.IsClient) + if (NetworkManager.Singleton != null) { - NetworkManager.Singleton.StartHost(); - Debug.Log("[AutoHost] 에디터 전용 호스트 자동 시작됨"); + if (!NetworkManager.Singleton.IsServer && !NetworkManager.Singleton.IsClient) + { + NetworkManager.Singleton.StartHost(); + Debug.Log("[AutoHost] Auto-host started"); + } + } + else + { + Debug.LogError("[AutoHost] NetworkManager not found!"); } } - else - { - Debug.LogError("[AutoHost] NetworkManager를 찾을 수 없습니다!"); - } + } + + private bool ShouldAutoStart() + { + if (!enableAutoHost) + return false; + +#if UNITY_EDITOR + return true; +#else + return !onlyInEditor; #endif } } \ No newline at end of file diff --git a/Assets/Scripts/Editor/NetworkConnectionHelperEditor.cs b/Assets/Scripts/Editor/NetworkConnectionHelperEditor.cs new file mode 100644 index 0000000..494aa8d --- /dev/null +++ b/Assets/Scripts/Editor/NetworkConnectionHelperEditor.cs @@ -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(); + 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()}"); + } + } +} diff --git a/Assets/Scripts/Editor/NetworkConnectionHelperEditor.cs.meta b/Assets/Scripts/Editor/NetworkConnectionHelperEditor.cs.meta new file mode 100644 index 0000000..372367f --- /dev/null +++ b/Assets/Scripts/Editor/NetworkConnectionHelperEditor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 36e75e477fa8d69459450bd36b5c6878 \ No newline at end of file diff --git a/Assets/Scripts/Editor/NetworkConnectionWindow.cs b/Assets/Scripts/Editor/NetworkConnectionWindow.cs new file mode 100644 index 0000000..92358bb --- /dev/null +++ b/Assets/Scripts/Editor/NetworkConnectionWindow.cs @@ -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("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()?.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(); + 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(); + 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(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; + } + } +} diff --git a/Assets/Scripts/Editor/NetworkConnectionWindow.cs.meta b/Assets/Scripts/Editor/NetworkConnectionWindow.cs.meta new file mode 100644 index 0000000..2bccbae --- /dev/null +++ b/Assets/Scripts/Editor/NetworkConnectionWindow.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b7b2610829b9bba4f801fb9d527963c9 \ No newline at end of file diff --git a/Assets/Scripts/Editor/NetworkUIBuilder.cs b/Assets/Scripts/Editor/NetworkUIBuilder.cs new file mode 100644 index 0000000..f1e4677 --- /dev/null +++ b/Assets/Scripts/Editor/NetworkUIBuilder.cs @@ -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.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(FindObjectsSortMode.None); + + if (canvases.Length > 0) + { + return canvases[0]; + } + + GameObject canvasObj = new GameObject("NetworkCanvas"); + Canvas canvas = canvasObj.AddComponent(); + canvas.renderMode = RenderMode.ScreenSpaceOverlay; + canvasObj.AddComponent(); + canvasObj.AddComponent(); + + return canvas; + } + + private static GameObject CreatePanel(Transform parent) + { + GameObject panel = new GameObject("NetworkJoinPanel"); + panel.transform.SetParent(parent, false); + + RectTransform rect = panel.AddComponent(); + 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.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(); + 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(); + bgImage.color = new Color(0.2f, 0.2f, 0.2f, 1f); + + InputField inputField = fieldObj.AddComponent(); + 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(); + 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(); + 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(); + bgImage.color = new Color(0.3f, 0.5f, 0.8f, 1f); + + Button button = buttonObj.AddComponent